Files
frontend-app-authoring/src/textbooks/textbook-form/TextbookForm.jsx
vladislavkeblysh 65f45f72f0 feat: [FC-0044] Textbooks Page (#890)
Implement Textbooks page.

---------

Co-authored-by: Glib Glugovskiy <glib.glugovskiy@raccoongang.com>
2024-04-30 18:38:43 -03:00

202 lines
8.2 KiB
JavaScript

import { useState } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { FieldArray, Formik } from 'formik';
import {
PictureAsPdf as PdfIcon,
Add as AddIcon,
DeleteOutline as DeleteIcon,
Upload as UploadIcon,
} from '@openedx/paragon/icons';
import {
ActionRow,
Button,
Form,
Icon,
IconButtonWithTooltip,
useToggle,
} from '@openedx/paragon';
import FormikControl from '../../generic/FormikControl';
import PromptIfDirty from '../../generic/promptIfDirty/PromptIfDirty';
import ModalDropzone from '../../generic/modal-dropzone/ModalDropzone';
import { useModel } from '../../generic/model-store';
import { UPLOAD_FILE_MAX_SIZE } from '../../constants';
import textbookFormValidationSchema from './validations';
import messages from './messages';
const TextbookForm = ({
closeTextbookForm,
initialFormValues,
onSubmit,
onSavingStatus,
courseId,
}) => {
const intl = useIntl();
const courseDetail = useModel('courseDetails', courseId);
const courseTitle = courseDetail ? courseDetail?.name : '';
const [currentTextbookIndex, setCurrentTextbookIndex] = useState(0);
const [isUploadModalOpen, openUploadModal, closeUploadModal] = useToggle(false);
const [selectedFile, setSelectedFile] = useState('');
const onCloseUploadModal = () => {
closeUploadModal();
setSelectedFile('');
};
const onUploadButtonClick = (index) => {
setCurrentTextbookIndex(index);
openUploadModal();
};
return (
<div className="textbook-form" data-testid="textbook-form">
<Formik
initialValues={initialFormValues}
validationSchema={textbookFormValidationSchema(intl)}
onSubmit={onSubmit}
validateOnBlur
validateOnMount
>
{({
values, handleSubmit, isValid, dirty, setFieldValue,
}) => (
<>
<Form.Group size="sm" className="form-field">
<Form.Label size="sm" className="font-weight-bold form-main-label text-black">
{intl.formatMessage(messages.tabTitleLabel)} *
</Form.Label>
<FormikControl
name="tab_title"
value={values.tab_title}
placeholder={intl.formatMessage(messages.tabTitlePlaceholder)}
/>
<Form.Control.Feedback className="form-helper-text">
{intl.formatMessage(messages.tabTitleHelperText)}
</Form.Control.Feedback>
</Form.Group>
<FieldArray
name="chapters"
render={(arrayHelpers) => (
<>
{!!values?.chapters.length && values.chapters.map(({ title, url }, index) => (
<div className="form-chapters-fields" data-testid="form-chapters-fields">
<Form.Group size="sm" className="form-field">
<Form.Label size="sm" className="form-label font-weight-bold required text-black">
{intl.formatMessage(messages.chapterTitleLabel)} *
</Form.Label>
<FormikControl
name={`chapters[${index}].title`}
value={title}
placeholder={intl.formatMessage(messages.chapterTitlePlaceholder, { value: index + 1 })}
/>
<Form.Control.Feedback className="form-helper-text">
{intl.formatMessage(messages.chapterTitleHelperText)}
</Form.Control.Feedback>
</Form.Group>
<Form.Group size="sm" className="form-field">
<div className="d-flex align-items-center mb-1">
<Form.Label size="sm" className="font-weight-bold mb-0 text-black">
{intl.formatMessage(messages.chapterUrlLabel)} *
</Form.Label>
<IconButtonWithTooltip
size="sm"
className="ml-auto field-icon-button"
tooltipContent={intl.formatMessage(messages.uploadButtonTooltip)}
src={UploadIcon}
iconAs={Icon}
data-testid="chapter-upload-button"
alt={intl.formatMessage(messages.uploadButtonAlt)}
onClick={() => onUploadButtonClick(index)}
/>
<IconButtonWithTooltip
size="sm"
className="field-icon-button"
tooltipContent={intl.formatMessage(messages.deleteButtonTooltip)}
src={DeleteIcon}
iconAs={Icon}
data-testid="chapter-delete-button"
alt={intl.formatMessage(messages.deleteButtonAlt)}
onClick={() => arrayHelpers.remove(index)}
/>
</div>
<FormikControl
name={`chapters[${index}].url`}
value={url}
placeholder={intl.formatMessage(messages.chapterUrlPlaceholder)}
/>
<Form.Control.Feedback className="form-helper-text">
{intl.formatMessage(messages.chapterUrlHelperText)}
</Form.Control.Feedback>
</Form.Group>
</div>
))}
<div>
{!values.chapters.length && (
<Form.Control.Feedback className="pgn__form-text-invalid mb-2">
{intl.formatMessage(messages.addChapterHelperText)}
</Form.Control.Feedback>
)}
<Button
variant="outline-primary"
className="w-100"
iconBefore={AddIcon}
onClick={() => arrayHelpers.push({ title: '', url: '' })}
>
{intl.formatMessage(messages.addChapterButton)}
</Button>
</div>
</>
)}
/>
<ActionRow>
<Button variant="tertiary" onClick={closeTextbookForm} data-testid="cancel-button">
{intl.formatMessage(messages.cancelButton)}
</Button>
<Button onClick={handleSubmit} disabled={!isValid} type="submit">
{intl.formatMessage(messages.saveButton)}
</Button>
</ActionRow>
<ModalDropzone
isOpen={isUploadModalOpen}
onClose={onCloseUploadModal}
onCancel={onCloseUploadModal}
onChange={(value) => setFieldValue(`chapters[${currentTextbookIndex}].url`, value)}
fileTypes={['pdf']}
modalTitle={intl.formatMessage(messages.uploadModalTitle, { courseName: courseTitle })}
imageDropzoneText={intl.formatMessage(messages.uploadModalDropzoneText)}
imageHelpText={intl.formatMessage(messages.uploadModalHelperText)}
onSavingStatus={onSavingStatus}
invalidFileSizeMore={intl.formatMessage(
messages.uploadModalFileInvalidSizeText,
{ maxSize: UPLOAD_FILE_MAX_SIZE / (1000 * 1000) },
)}
onSelectFile={setSelectedFile}
previewComponent={(
<div className="modal-preview">
<Icon src={PdfIcon} className="modal-preview-icon" />
<span className="modal-preview-text">{selectedFile}</span>
</div>
)}
maxSize={UPLOAD_FILE_MAX_SIZE}
/>
<PromptIfDirty dirty={dirty} />
</>
)}
</Formik>
</div>
);
};
TextbookForm.propTypes = {
closeTextbookForm: PropTypes.func.isRequired,
initialFormValues: PropTypes.shape({}).isRequired,
onSubmit: PropTypes.func.isRequired,
onSavingStatus: PropTypes.func.isRequired,
courseId: PropTypes.string.isRequired,
};
export default TextbookForm;