diff --git a/packages/strapi-plugin-upload/admin/src/components/InputUploadURL/index.js b/packages/strapi-plugin-upload/admin/src/components/InputUploadURL/index.js index 3b0da87fe0..8ee7edcf6c 100644 --- a/packages/strapi-plugin-upload/admin/src/components/InputUploadURL/index.js +++ b/packages/strapi-plugin-upload/admin/src/components/InputUploadURL/index.js @@ -5,10 +5,11 @@ import PropTypes from 'prop-types'; import Wrapper from './Wrapper'; import { getTrad } from '../../utils'; -const InputUploadURL = ({ onChange, value }) => { +const InputUploadURL = ({ errors, onChange, value }) => { const { formatMessage } = useGlobalContext(); const label = formatMessage({ id: getTrad('input.url.label') }); const description = formatMessage({ id: getTrad('input.url.description') }); + const error = errors ? formatMessage({ id: errors.id }, { number: errors.number }) : null; return ( @@ -16,6 +17,7 @@ const InputUploadURL = ({ onChange, value }) => {
{ }; InputUploadURL.defaultProps = { + errors: null, onChange: () => {}, value: [], }; InputUploadURL.propTypes = { + errors: PropTypes.object, onChange: PropTypes.func, value: PropTypes.arrayOf(PropTypes.string), }; diff --git a/packages/strapi-plugin-upload/admin/src/components/UploadForm/index.js b/packages/strapi-plugin-upload/admin/src/components/UploadForm/index.js index 7661e1a6ad..8333063aca 100644 --- a/packages/strapi-plugin-upload/admin/src/components/UploadForm/index.js +++ b/packages/strapi-plugin-upload/admin/src/components/UploadForm/index.js @@ -8,6 +8,7 @@ import ModalSection from '../ModalSection'; const UploadForm = ({ addFilesToUpload, filesToDownload, + formErrors, onChange, setShouldDisplayNextButton, }) => { @@ -32,7 +33,9 @@ const UploadForm = ({ {to => ( {to === 'computer' && } - {to === 'url' && } + {to === 'url' && ( + + )} )} @@ -42,6 +45,7 @@ const UploadForm = ({ UploadForm.defaultProps = { addFilesToUpload: () => {}, filesToDownload: [], + formErrors: null, onChange: () => {}, setShouldDisplayNextButton: () => {}, }; @@ -49,6 +53,7 @@ UploadForm.defaultProps = { UploadForm.propTypes = { addFilesToUpload: PropTypes.func, filesToDownload: PropTypes.arrayOf(PropTypes.string), + formErrors: PropTypes.object, onChange: PropTypes.func, setShouldDisplayNextButton: PropTypes.func, }; diff --git a/packages/strapi-plugin-upload/admin/src/containers/InputModalStepper/InputModalStepper.js b/packages/strapi-plugin-upload/admin/src/containers/InputModalStepper/InputModalStepper.js index 4b43f4105e..25488f5c81 100644 --- a/packages/strapi-plugin-upload/admin/src/containers/InputModalStepper/InputModalStepper.js +++ b/packages/strapi-plugin-upload/admin/src/containers/InputModalStepper/InputModalStepper.js @@ -21,6 +21,7 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => { filesToDownload, filesToUpload, fileToEdit, + formErrors, goTo, handleAbortUpload, handleCancelFileToUpload, @@ -236,6 +237,7 @@ const InputModalStepper = ({ isOpen, onToggle, onInputMediaChange }) => { filesToDownload={filesToDownload} filesToUpload={filesToUpload} fileToEdit={fileToEdit} + formErrors={formErrors} isEditingUploadedFile={currentStep === 'edit'} isFormDisabled={isFormDisabled} onAbortUpload={handleAbortUpload} diff --git a/packages/strapi-plugin-upload/admin/src/containers/InputModalStepperProvider/index.js b/packages/strapi-plugin-upload/admin/src/containers/InputModalStepperProvider/index.js index 430c4e8878..e0e71f142e 100644 --- a/packages/strapi-plugin-upload/admin/src/containers/InputModalStepperProvider/index.js +++ b/packages/strapi-plugin-upload/admin/src/containers/InputModalStepperProvider/index.js @@ -1,14 +1,16 @@ -import React, { useReducer, useEffect } from 'react'; +import React, { useReducer, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { request, generateSearchFromFilters } from 'strapi-helper-plugin'; -import { get } from 'lodash'; +import { get, isEmpty } from 'lodash'; import axios from 'axios'; import pluginId from '../../pluginId'; import { getFilesToDownload, getRequestUrl, + getYupError, compactParams, createNewFilesToUploadArray, + urlSchema, } from '../../utils'; import InputModalStepperContext from '../../contexts/InputModal/InputModalDataManager'; import init from './init'; @@ -28,6 +30,7 @@ const InputModalStepperProvider = ({ selectedFiles, step, }) => { + const [formErrors, setFormErrors] = useState(null); const [reducerState, dispatch] = useReducer(reducer, initialState, state => init({ ...state, @@ -46,7 +49,7 @@ const InputModalStepperProvider = ({ }, }) ); - const { params, filesToUpload, fileToEdit } = reducerState; + const { params, filesToDownload, filesToUpload, fileToEdit } = reducerState; useEffect(() => { if (isOpen) { @@ -104,11 +107,24 @@ const InputModalStepperProvider = ({ }); }; - const handleClickNextButton = () => { - dispatch({ - type: 'ADD_URLS_TO_FILES_TO_UPLOAD', - nextStep: 'upload', - }); + const handleClickNextButton = async () => { + try { + await urlSchema.validate( + { filesToDownload: filesToDownload.filter(url => !isEmpty(url)) }, + { abortEarly: false } + ); + + setFormErrors(null); + // Navigate to next step + dispatch({ + type: 'ADD_URLS_TO_FILES_TO_UPLOAD', + nextStep: 'upload', + }); + } catch (err) { + const formattedErrors = getYupError(err); + + setFormErrors(formattedErrors.filesToDownload); + } }; const handleFileToEditChange = ({ target: { name, value } }) => { @@ -116,6 +132,8 @@ const InputModalStepperProvider = ({ let type = 'ON_CHANGE'; if (name === 'url') { + setFormErrors(null); + val = value.split('\n'); type = 'ON_CHANGE_URLS_TO_DOWNLOAD'; } @@ -381,6 +399,7 @@ const InputModalStepperProvider = ({ addFilesToUpload, downloadFiles, fetchMediaLib, + formErrors, goTo, handleAbortUpload, handleAllFilesSelection, diff --git a/packages/strapi-plugin-upload/admin/src/containers/ModalStepper/index.js b/packages/strapi-plugin-upload/admin/src/containers/ModalStepper/index.js index bacf61845a..461c843901 100644 --- a/packages/strapi-plugin-upload/admin/src/containers/ModalStepper/index.js +++ b/packages/strapi-plugin-upload/admin/src/containers/ModalStepper/index.js @@ -5,7 +5,7 @@ import { isEqual, isEmpty, get } from 'lodash'; import { Modal, ModalFooter, PopUpWarning, useGlobalContext, request } from 'strapi-helper-plugin'; import { Button } from '@buffetjs/core'; import pluginId from '../../pluginId'; -import { getFilesToDownload, getTrad } from '../../utils'; +import { getFilesToDownload, getTrad, getYupError, urlSchema } from '../../utils'; import ModalHeader from '../../components/ModalHeader'; import stepper from './stepper'; import init from './init'; @@ -23,6 +23,7 @@ const ModalStepper = ({ const [isWarningDeleteOpen, setIsWarningDeleteOpen] = useState(false); const [shouldDeleteFile, setShouldDeleteFile] = useState(false); const [isFormDisabled, setIsFormDisabled] = useState(false); + const [formErrors, setFormErrors] = useState(null); const [displayNextButton, setDisplayNextButton] = useState(false); const [reducerState, dispatch] = useReducer(reducer, initialState, init); const { currentStep, fileToEdit, filesToDownload, filesToUpload } = reducerState.toJS(); @@ -150,6 +151,8 @@ const ModalStepper = ({ let type = 'ON_CHANGE'; if (name === 'url') { + setFormErrors(null); + val = value.split('\n'); type = 'ON_CHANGE_URLS_TO_DOWNLOAD'; } @@ -166,12 +169,24 @@ const ModalStepper = ({ toggleModalWarning(); }; - const handleClickNextButton = () => { - // Navigate to next step - dispatch({ - type: 'ADD_URLS_TO_FILES_TO_UPLOAD', - nextStep: next, - }); + const handleClickNextButton = async () => { + try { + await urlSchema.validate( + { filesToDownload: filesToDownload.filter(url => !isEmpty(url)) }, + { abortEarly: false } + ); + + setFormErrors(null); + // Navigate to next step + dispatch({ + type: 'ADD_URLS_TO_FILES_TO_UPLOAD', + nextStep: next, + }); + } catch (err) { + const formattedErrors = getYupError(err); + + setFormErrors(formattedErrors.filesToDownload); + } }; const handleClickDeleteFile = async () => { @@ -197,6 +212,7 @@ const ModalStepper = ({ onClosed(); setIsFormDisabled(false); setDisplayNextButton(false); + setFormErrors(null); dispatch({ type: 'RESET_PROPS', @@ -416,6 +432,7 @@ const ModalStepper = ({ fileToEdit={fileToEdit} filesToDownload={filesToDownload} filesToUpload={filesToUpload} + formErrors={formErrors} components={components} isEditingUploadedFile={currentStep === 'edit'} isFormDisabled={isFormDisabled} diff --git a/packages/strapi-plugin-upload/admin/src/translations/en.json b/packages/strapi-plugin-upload/admin/src/translations/en.json index 87c9f4394f..9a392e5f57 100644 --- a/packages/strapi-plugin-upload/admin/src/translations/en.json +++ b/packages/strapi-plugin-upload/admin/src/translations/en.json @@ -14,6 +14,8 @@ "form.input.decription.file-alt": "This text will be displayed if the asset can’t be shown.", "form.input.label.file-caption": "Caption", "form.input.label.file-name": "File name", + "form.upload-url.error.url.invalid": "One URL is invalid", + "form.upload-url.error.url.invalids": "{number} URLs are invalids", "header.actions.upload-assets": "Upload assets", "header.content.assets-empty": "No asset", "header.content.assets-multiple": "{number} assets", diff --git a/packages/strapi-plugin-upload/admin/src/utils/getYupError.js b/packages/strapi-plugin-upload/admin/src/utils/getYupError.js new file mode 100644 index 0000000000..e9f7418c8c --- /dev/null +++ b/packages/strapi-plugin-upload/admin/src/utils/getYupError.js @@ -0,0 +1,17 @@ +import { get } from 'lodash'; + +const getYupError = error => { + return get(error, 'inner', []).reduce((acc, curr) => { + acc[ + curr.path + .split('[') + .join('.') + .split(']') + .join('') + ] = { id: curr.message, number: curr.params.wrongURLsNumber }; + + return acc; + }, {}); +}; + +export default getYupError; diff --git a/packages/strapi-plugin-upload/admin/src/utils/index.js b/packages/strapi-plugin-upload/admin/src/utils/index.js index fdd6569cb3..e35f9c9bc8 100644 --- a/packages/strapi-plugin-upload/admin/src/utils/index.js +++ b/packages/strapi-plugin-upload/admin/src/utils/index.js @@ -13,4 +13,6 @@ export { default as getFilesToDownload } from './getFilesToDownload'; export { default as getRequestUrl } from './getRequestUrl'; export { default as getTrad } from './getTrad'; export { default as getType } from './getType'; +export { default as getYupError } from './getYupError'; export { default as ItemTypes } from './ItemTypes'; +export { default as urlSchema } from './urlYupSchema'; diff --git a/packages/strapi-plugin-upload/admin/src/utils/urlYupSchema.js b/packages/strapi-plugin-upload/admin/src/utils/urlYupSchema.js new file mode 100644 index 0000000000..dee3a26e53 --- /dev/null +++ b/packages/strapi-plugin-upload/admin/src/utils/urlYupSchema.js @@ -0,0 +1,41 @@ +/* eslint-disable no-template-curly-in-string */ +import * as yup from 'yup'; +import { translatedErrors as errorsTrads } from 'strapi-helper-plugin'; +import getTrad from './getTrad'; + +const urlSchema = yup.object().shape({ + filesToDownload: yup + .array() + .of(yup.string()) + + .test({ + name: 'isUrlValid', + message: '${path}', + test(values) { + const filtered = values.filter(val => { + return !val.startsWith('http'); + }); + + const filteredLength = filtered.length; + + if (filteredLength === 0) { + return true; + } + + const errorMessage = + filteredLength > 1 + ? 'form.upload-url.error.url.invalids' + : 'form.upload-url.error.url.invalid'; + + return this.createError({ + path: this.path, + message: getTrad(errorMessage), + params: { wrongURLsNumber: filtered.length }, + }); + }, + }) + .min(0, errorsTrads.min) + .max(20, errorsTrads.max), +}); + +export default urlSchema;