diff --git a/packages/strapi-admin/admin/src/components/EmptyList/Wrapper.js b/packages/strapi-admin/admin/src/components/EmptyList/Wrapper.js index 708d0a8947..97ba97bf7e 100644 --- a/packages/strapi-admin/admin/src/components/EmptyList/Wrapper.js +++ b/packages/strapi-admin/admin/src/components/EmptyList/Wrapper.js @@ -15,7 +15,6 @@ const Wrapper = styled.div` line-height: normal; } p { - line-height: normal; &:first-of-type { font-size: 18px; font-weight: 600; diff --git a/packages/strapi-admin/admin/src/components/EventInput/Wrapper.js b/packages/strapi-admin/admin/src/components/EventInput/Wrapper.js index 1c4dbeb669..5de576510f 100644 --- a/packages/strapi-admin/admin/src/components/EventInput/Wrapper.js +++ b/packages/strapi-admin/admin/src/components/EventInput/Wrapper.js @@ -5,13 +5,14 @@ */ import styled from 'styled-components'; +import { sizes } from '@buffetjs/styles'; const Wrapper = styled.div` padding-top: 3px; padding-bottom: 8px; table { width: 100%; - border-radius: 3px; + border-radius: ${sizes.borderRadius}; overflow: hidden; } tr { @@ -45,8 +46,8 @@ const Wrapper = styled.div` } } tbody { - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; + border-bottom-left-radius: ${sizes.borderRadius}; + border-bottom-right-radius: ${sizes.borderRadius}; box-shadow: inset 0px 0px 0px 1px #f6f6f6; td { height: 54px; diff --git a/packages/strapi-admin/admin/src/components/HeadersInput/Wrapper.js b/packages/strapi-admin/admin/src/components/HeadersInput/Wrapper.js index 7340025cf7..82c05809f1 100644 --- a/packages/strapi-admin/admin/src/components/HeadersInput/Wrapper.js +++ b/packages/strapi-admin/admin/src/components/HeadersInput/Wrapper.js @@ -5,13 +5,14 @@ */ import styled from 'styled-components'; +import { sizes } from '@buffetjs/styles'; const Wrapper = styled.div` margin-top: 12px; padding: 23px 24px 26px 24px; background-color: #fafafb; - border-radius: 3px; - ul { + border-radius: ${sizes.borderRadius}; + > ul { margin-bottom: 0; padding: 0; list-style-type: none; @@ -29,67 +30,68 @@ const Wrapper = styled.div` margin-right: 10px; } } - } - li { - position: relative; - padding-right: 30px; - &:not(:first-of-type) { - margin-bottom: 20px; - } - &:last-of-type { - margin-bottom: 6px; - } - > section { - display: inline-block; - width: 50%; - vertical-align: top; - &:nth-child(odd) { - padding-right: 15px; + li { + position: relative; + padding-right: 30px; + &:not(:first-of-type) { + margin-bottom: 20px; } - &:nth-child(even) { - padding-left: 15px; + &:last-of-type { + margin-bottom: 6px; } - > p { - font-size: 13px; - color: #333740; - font-weight: 500; - } - > div:first-of-type { - height: 34px; - > div:first-of-type { - height: 34px; - min-height: 34px; - border: 1px solid #e3e9f3; - border-radius: 2px; + > section { + display: inline-block; + width: 50%; + vertical-align: top; + &:nth-child(odd) { + padding-right: 15px; + } + &:nth-child(even) { + padding-left: 15px; + } + > p { font-size: 13px; color: #333740; - align-items: normal; + font-weight: 500; + } + > div:first-of-type { + height: 34px; > div:first-of-type { - height: 32px; - padding: 0 1rem; + height: 34px; + min-height: 34px; + border: 1px solid #e3e9f3; + border-radius: 2px; + font-size: 13px; + color: #333740; + align-items: normal; + > div:first-of-type { + height: 32px; + padding: 0 1rem; + } + > div:last-of-type { + display: none; + } + &:hover { + cursor: text; + } } - > div:last-of-type { - display: none; - } - &:hover { - cursor: text; + > span + div:first-of-type { + border-color: #78caff; + box-shadow: none; } } - > span + div:first-of-type { - border-color: #78caff; - box-shadow: none; - } - } - & + div { - position: absolute; - top: 7px; - right: 0; - button { - margin: 0; + & + div { + position: absolute; + top: 7px; + right: 0; + button { + margin: 0; + } } } } } + .bordered { input { border: 1px solid #f64d0a; diff --git a/packages/strapi-admin/admin/src/components/HeadersInput/index.js b/packages/strapi-admin/admin/src/components/HeadersInput/index.js index fe7c201a86..1dc7f1d6df 100644 --- a/packages/strapi-admin/admin/src/components/HeadersInput/index.js +++ b/packages/strapi-admin/admin/src/components/HeadersInput/index.js @@ -7,6 +7,7 @@ import { CircleButton } from 'strapi-helper-plugin'; import { InputText } from '@buffetjs/core'; import { Plus } from '@buffetjs/icons'; +import borderColor from './utils/borderColor'; import keys from './keys'; import Wrapper from './Wrapper'; @@ -19,8 +20,8 @@ const HeadersInput = ({ onRemove, value, }) => { - const optionFormat = value => ({ value: value, label: value }); - const options = keys.map(key => optionFormat(key)); + const formatOption = value => ({ value: value, label: value }); + const options = keys.map(key => formatOption(key)); const handleBlur = () => onBlur({ target: { name, value } }); @@ -33,28 +34,14 @@ const HeadersInput = ({ } }; - const handleClick = () => onClick(name); - - const handleRemoveItem = index => { - const event = index === 0 && value.length === 1 ? 'clear' : 'remove'; - onRemove({ event, index }); - }; - const customStyles = hasError => { - const selectBorder = isFocused => { - if (isFocused) { - return '1px solid #78caff !important'; - } - if (hasError) { - return '1px solid #F64D0A !important'; - } - return '1px solid #E3E9F3 !important'; - }; - return { control: (base, state) => ({ ...base, - border: selectBorder(state.isFocused), + border: `1px solid ${borderColor({ + isFocused: state.isFocused, + hasError, + })} !important`, borderRadius: '2px !important', }), menu: base => { @@ -121,7 +108,7 @@ const HeadersInput = ({ name={`${name}.${index}.key`} options={options} styles={customStyles(entryErrors && entryErrors.key)} - value={optionFormat(key)} + value={formatOption(key)} />
@@ -137,14 +124,14 @@ const HeadersInput = ({ handleRemoveItem(index)} + onClick={() => onRemove(index)} /> ); })} - diff --git a/packages/strapi-admin/admin/src/components/HeadersInput/utils/borderColor.js b/packages/strapi-admin/admin/src/components/HeadersInput/utils/borderColor.js new file mode 100644 index 0000000000..6e9ecad74c --- /dev/null +++ b/packages/strapi-admin/admin/src/components/HeadersInput/utils/borderColor.js @@ -0,0 +1,11 @@ +const borderColor = ({ isFocused = false, hasError = false }) => { + if (isFocused) { + return '#78caff'; + } + if (hasError) { + return '#F64D0A'; + } + return '#E3E9F3'; +}; + +export default borderColor; diff --git a/packages/strapi-admin/admin/src/components/Inputs/index.js b/packages/strapi-admin/admin/src/components/Inputs/index.js index 3c3ac66e66..993f71ace9 100644 --- a/packages/strapi-admin/admin/src/components/Inputs/index.js +++ b/packages/strapi-admin/admin/src/components/Inputs/index.js @@ -7,7 +7,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { ErrorMessage, Label } from '@buffetjs/styles'; -import { Error, InputText } from '@buffetjs/core'; +import { Error } from '@buffetjs/core'; import HeadersInput from '../HeadersInput'; import EventInput from '../EventInput'; @@ -71,29 +71,15 @@ function Inputs({ return ( <> - {type === 'events' ? ( - { - handleChange(e); - onBlur(e); - }} - value={value} - /> - ) : ( - - )} - {hasError && ( - - - - )} + { + handleChange(e); + onBlur(e); + }} + value={value} + /> + {hasError && {error}} ); }} diff --git a/packages/strapi-admin/admin/src/components/ListRow/index.js b/packages/strapi-admin/admin/src/components/ListRow/index.js index 4c979c9c01..dc61e679a5 100644 --- a/packages/strapi-admin/admin/src/components/ListRow/index.js +++ b/packages/strapi-admin/admin/src/components/ListRow/index.js @@ -28,7 +28,7 @@ function ListRow({ const links = [ { icon: 'pencil', - onClick: () => handleEditClick(), + onClick: () => onEditClick(id), }, { icon: 'trash', @@ -41,26 +41,19 @@ function ListRow({ const isChecked = itemsToDelete.includes(id); - const handleEditClick = () => onEditClick(id); - - const handleEnabledChange = ({ target: { value } }) => - onEnabledChange(value, id); - - const handleCheckChange = ({ target: { value } }) => onCheckChange(value, id); - - const handleDeleteConfirm = () => { - onDeleteCLick(id); + const handleDeleteConfirm = async () => { + await onDeleteCLick(id); setShowModal(false); }; return ( - + onEditClick(id)}> e.stopPropagation()} - onChange={handleCheckChange} + onChange={({ target: { value } }) => onCheckChange(value, id)} /> @@ -74,7 +67,7 @@ function ListRow({ onEnabledChange(value, id)} > diff --git a/packages/strapi-admin/admin/src/components/TriggerContainer/index.js b/packages/strapi-admin/admin/src/components/TriggerContainer/index.js index 41aa4bc1f2..45c999d6da 100644 --- a/packages/strapi-admin/admin/src/components/TriggerContainer/index.js +++ b/packages/strapi-admin/admin/src/components/TriggerContainer/index.js @@ -15,13 +15,17 @@ const TriggerContainer = ({ isPending, onCancel, response }) => { -

Test-trigger

+

+ {formatMessage({ + id: `Settings.webhooks.trigger.test`, + })} +

{isPending && ( <>

- + {formatMessage({ id: `Settings.webhooks.trigger.pending`, @@ -65,7 +69,12 @@ const TriggerContainer = ({ isPending, onCancel, response }) => {

- Error {statusCode} + + {formatMessage({ + id: `Settings.error`, + })}{' '} + {statusCode} +

diff --git a/packages/strapi-admin/admin/src/containers/Admin/index.js b/packages/strapi-admin/admin/src/containers/Admin/index.js index b48e03b2f8..52488cec69 100644 --- a/packages/strapi-admin/admin/src/containers/Admin/index.js +++ b/packages/strapi-admin/admin/src/containers/Admin/index.js @@ -113,10 +113,6 @@ export class Admin extends React.Component { }, []); }; - renderMarketPlace = props => ; - - renderSettings = props => ; - renderPluginDispatcher = props => { // NOTE: Send the needed props instead of everything... @@ -183,10 +179,12 @@ export class Admin extends React.Component { /> this.renderRoute(props, Marketplace)} + /> + this.renderRoute(props, SettingsPage)} /> - diff --git a/packages/strapi-admin/admin/src/containers/SettingsPage/index.js b/packages/strapi-admin/admin/src/containers/SettingsPage/index.js index 885d531b3b..c2c5b88f8b 100644 --- a/packages/strapi-admin/admin/src/containers/SettingsPage/index.js +++ b/packages/strapi-admin/admin/src/containers/SettingsPage/index.js @@ -32,9 +32,7 @@ function SettingsPage() {
{menuItems.map(item => { - return ( - - ); + return ; })}
diff --git a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/Wrapper.js b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/Wrapper.js index bcbfa4ab42..aeae4a63b5 100644 --- a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/Wrapper.js +++ b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/Wrapper.js @@ -32,6 +32,8 @@ const Wrapper = styled.div` } span svg { margin-top: -2px; + margin-right: -10px; + margin-bottom: -5px; } } } diff --git a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/index.js b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/index.js index 6437c996c8..73b2f183d6 100644 --- a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/index.js +++ b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/index.js @@ -5,9 +5,9 @@ */ import React, { useEffect, useReducer, useCallback, useState } from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; -import { get, isEmpty, isEqual, set, setWith } from 'lodash'; -import { Header } from '@buffetjs/custom'; +import { useHistory, useParams } from 'react-router-dom'; +import { get, isEmpty, isEqual, set } from 'lodash'; +import { Header, Inputs as InputsIndex } from '@buffetjs/custom'; import { Play } from '@buffetjs/icons'; import { request, @@ -21,7 +21,8 @@ import TriggerContainer from '../../../components/TriggerContainer'; import reducer, { initialState } from './reducer'; import form from './utils/form'; -import createYupSchema from './utils/schema'; +import schema from './utils/schema'; +import { cleanHeaders, cleanData, cleanErrors } from './utils/formatData'; import Wrapper from './Wrapper'; @@ -29,31 +30,105 @@ function EditView() { const { formatMessage } = useGlobalContext(); const [submittedOnce, setSubmittedOnce] = useState(false); const [reducerState, dispatch] = useReducer(reducer, initialState); - const location = useLocation(); const { push } = useHistory(); const { formErrors, - modifiedWebhook, - initialWebhook, + modifiedData, + initialData, isTriggering, triggerResponse, } = reducerState.toJS(); - const { name } = modifiedWebhook; - - const id = location.pathname.split('/')[3]; + const { name } = modifiedData; + const { id } = useParams(); const isCreating = id === 'create'; const abortController = new AbortController(); - const { signal } = abortController; useEffect(() => { if (!isCreating) { fetchData(); + + return () => { + abortController.abort(); + }; } - }, [fetchData, isCreating]); + }, [abortController, fetchData, isCreating]); + + /* Header props */ + + const areActionDisabled = + isEqual(initialData, modifiedData) || Object.keys(formErrors).length > 0; + + const headerTitle = isCreating + ? formatMessage({ + id: `Settings.webhooks.create`, + }) + : name; + + const isTriggerActionDisabled = + isCreating || (!isCreating && !areActionDisabled) || isTriggering; + + const actions = [ + { + color: 'primary', + disabled: isTriggerActionDisabled, + label: formatMessage({ + id: `Settings.webhooks.trigger`, + }), + onClick: handleTrigger, + style: { + padding: '0 15px', + }, + title: isTriggerActionDisabled + ? formatMessage({ + id: `Settings.webhooks.trigger.save`, + }) + : null, + type: 'button', + icon: ( + + ), + }, + { + color: 'cancel', + disabled: areActionDisabled, + label: formatMessage({ + id: `app.components.Button.reset`, + }), + onClick: handleReset, + style: { + padding: '0 20px', + }, + type: 'button', + }, + { + color: 'success', + disabled: areActionDisabled, + label: formatMessage({ + id: `app.components.Button.save`, + }), + style: { + minWidth: 140, + }, + type: 'submit', + }, + ]; + + const headerProps = { + title: { + label: headerTitle, + }, + actions: actions, + }; + + /* Data methods */ const fetchData = useCallback(async () => { try { @@ -72,115 +147,57 @@ function EditView() { } }, [id]); - const headerTitle = isCreating - ? formatMessage({ - id: `Settings.webhooks.create`, - }) - : name; - - const actionsAreDisabled = - isEqual(initialWebhook, modifiedWebhook) || - Object.keys(formErrors).length > 0; - - const triggerActionIsDisabled = - isCreating || (!isCreating && !actionsAreDisabled); - - const handleTrigger = async () => { - dispatch({ - type: 'ON_TRIGGER', - }); - - try { - const { data } = await request(`/admin/webhooks/${id}/trigger`, { - method: 'POST', - signal, - }); - - dispatch({ - type: 'TRIGGER_SUCCEEDED', - response: data, - }); - } catch (err) { - if (err.code !== 20) { - strapi.notification.error('notification.error'); - } + const submitForm = () => { + if (!isCreating) { + updateWebhook(); + } else { + createWebhooks(); } }; - const onCancelTrigger = () => { - abortController.abort(); + const createWebhooks = async () => { + try { + await request(`/admin/webhooks`, { + method: 'POST', + body: cleanData(modifiedData), + }); - dispatch({ - type: 'ON_TRIGGER_CANCELED', - }); + strapi.notification.success(`notification.success`); + goBack(); + } catch (err) { + strapi.notification.error('notification.error'); + } }; + const updateWebhook = async () => { + try { + const body = cleanData(modifiedData); + delete body.id; + + await request(`/admin/webhooks/${id}`, { + method: 'PUT', + body, + }); + + fetchData(); + + strapi.notification.error('notification.form.success.fields'); + } catch (err) { + strapi.notification.error('notification.error'); + } + }; + + /* Form user interactions */ + const handleReset = () => dispatch({ - type: 'RESET', + type: 'RESET_FORM', }); - const actions = [ - { - color: 'primary', - disabled: triggerActionIsDisabled, - label: formatMessage({ - id: `Settings.webhooks.trigger`, - }), - onClick: () => { - handleTrigger(); - }, - style: { - padding: '0 15px', - }, - title: triggerActionIsDisabled - ? formatMessage({ - id: `Settings.webhooks.trigger.save`, - }) - : null, - type: 'button', - icon: ( - - ), - }, - { - color: 'cancel', - disabled: actionsAreDisabled, - label: formatMessage({ - id: `app.components.Button.reset`, - }), - onClick: () => handleReset(), - style: { - padding: '0 20px', - }, - type: 'button', - }, - { - color: 'success', - disabled: actionsAreDisabled, - label: formatMessage({ - id: `app.components.Button.save`, - }), - style: { - minWidth: 140, - }, - type: 'submit', - }, - ]; - - const headerProps = { - title: { - label: headerTitle, - }, - actions: actions, - }; - const handleBlur = () => { - if (submittedOnce) checkFormErrors(); + if (submittedOnce) { + checkFormErrors(); + } }; const handleChange = ({ target: { name, value } }) => { @@ -198,11 +215,10 @@ function EditView() { }); }; - const handleRemove = ({ event, index }) => { + const handleRemove = index => { dispatch({ type: 'ON_HEADER_REMOVE', index, - event, }); resetError('headers'); }; @@ -213,20 +229,14 @@ function EditView() { checkFormErrors(true); }; - const submitForm = () => { - if (!isCreating) { - updateWebhook(); - } else { - createWebhooks(); - } - }; + /* Validations */ const checkFormErrors = async (submit = false) => { - const webhookToCheck = modifiedWebhook; - set(webhookToCheck, 'headers', cleanHeaders()); + const webhookToCheck = modifiedData; + set(webhookToCheck, 'headers', cleanHeaders(modifiedData.headers)); try { - await createYupSchema(form).validate(webhookToCheck, { + await schema.validate(webhookToCheck, { abortEarly: false, }); @@ -238,18 +248,6 @@ function EditView() { } }; - const cleanHeaders = () => { - const { headers } = modifiedWebhook; - - if (Object.keys(headers).length === 1) { - const { key, value } = headers[0]; - if (key.length === 0 && value.length === 0) return []; - } - return headers; - }; - - const goBack = () => push('/settings/webhooks'); - const resetError = name => { const errors = formErrors; @@ -260,68 +258,66 @@ function EditView() { }; const setErrors = errors => { - const newErrors = Object.keys(errors).reduce((acc, curr) => { - const { id } = errors[curr]; - - setWith(acc, curr, id ? id : errors[curr], Object); - - return acc; - }, {}); - dispatch({ type: 'SET_ERRORS', - errors: newErrors, + errors: cleanErrors(errors), }); }; - const createWebhooks = async () => { + const errorMessage = error => { + if (!error) { + return null; + } + if (typeof error === 'string') { + return formatMessage({ + id: error, + }); + } + return error; + }; + + /* Trigger events */ + + const handleTrigger = async () => { + dispatch({ + type: 'IS_TRIGGERING', + }); + try { - await request(`/admin/webhooks`, { + const { data } = await request(`/admin/webhooks/${id}/trigger`, { method: 'POST', - body: formatWebhook(), + signal, }); - strapi.notification.success(`notification.success`); - goBack(); + dispatch({ + type: 'TRIGGER_SUCCEEDED', + response: data, + }); } catch (err) { - strapi.notification.error('notification.error'); + if (err.code !== 20) { + strapi.notification.error('notification.error'); + } + dispatch({ + type: 'IS_TRIGGERING', + }); } }; - const updateWebhook = async () => { - try { - const body = formatWebhook(); - delete body.id; + const onCancelTrigger = () => { + abortController.abort(); - await request(`/admin/webhooks/${id}`, { - method: 'PUT', - body, - }); - - fetchData(); - - strapi.notification.error('notification.form.success.fields'); - } catch (err) { - strapi.notification.error('notification.error'); - } + dispatch({ + type: 'ON_TRIGGER_CANCELED', + }); }; - // utils - const formatWebhook = () => { - const webhooks = modifiedWebhook; - set(webhooks, 'headers', unformatHeaders(cleanHeaders())); - return webhooks; - }; + /* Nav */ - const unformatHeaders = headers => { - return headers.reduce((obj, item) => { - const { key, value } = item; - return { - ...obj, - [key]: value, - }; - }, {}); - }; + const goBack = () => push('/settings/webhooks'); + + const renderHeadersInput = () => props => ( + + ); return ( @@ -343,16 +339,18 @@ function EditView() { {Object.keys(form).map(key => { return (
-
); diff --git a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/reducer.js b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/reducer.js index 8db3420623..0b57150c78 100644 --- a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/reducer.js +++ b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/reducer.js @@ -1,79 +1,74 @@ import { fromJS } from 'immutable'; -import { cloneDeep, set, get } from 'lodash'; +import { get } from 'lodash'; + +const header = { key: '', value: '' }; const initialWebhook = { + events: [], + headers: [header], name: null, url: null, - headers: [{ key: '', value: '' }], - events: [], }; const initialState = fromJS({ formErrors: {}, - initialWebhook: initialWebhook, - modifiedWebhook: initialWebhook, - triggerResponse: {}, + initialData: initialWebhook, isTriggering: false, + modifiedData: initialWebhook, + triggerResponse: {}, }); const reducer = (state, action) => { switch (action.type) { + case 'ADD_NEW_HEADER': + return state.updateIn(['modifiedData', ...action.keys], arr => + arr.push(fromJS(header)) + ); case 'GET_DATA_SUCCEEDED': { - const data = cloneDeep(action.data); - const headers = get(data, 'headers'); + const headers = get(action, ['data', 'headers'], {}); + let formattedHeaders = [header]; if (Object.keys(headers).length > 0) { - const newHeaders = fromJS( - Object.keys(headers).map(key => { - return { key: key, value: headers[key] }; - }) - ); - set(data, ['headers'], newHeaders); - } else { - set(data, ['headers'], get(initialWebhook, 'headers')); + formattedHeaders = Object.keys(headers).map(key => { + return { key: key, value: headers[key] }; + }); } + const data = fromJS(action.data).update('headers', () => + fromJS(formattedHeaders) + ); + return state - .update('initialWebhook', () => fromJS(data)) - .update('modifiedWebhook', () => fromJS(data)); + .update('initialData', () => data) + .update('modifiedData', () => data); } + case 'IS_TRIGGERING': + return state.update('isTriggering', isTriggering => !isTriggering); + case 'ON_CHANGE': + return state.updateIn( + ['modifiedData', ...action.keys], + () => action.value + ); + case 'ON_HEADER_REMOVE': { + return state.updateIn(['modifiedData', 'headers'], headers => { + if (headers.size === 1) { + return fromJS([header]); + } + return headers.remove(action.index); + }); + } + case 'ON_TRIGGER_CANCELED': + return state + .update('isTriggering', () => false) + .set('triggerResponse', fromJS({})); + case 'RESET_FORM': + return state.update('modifiedData', () => state.get('initialData')); + case 'SET_ERRORS': + return state.update('formErrors', () => fromJS(action.errors)); case 'TRIGGER_SUCCEEDED': return state .update('triggerResponse', () => fromJS(action.response)) .update('isTriggering', () => false); - case 'ON_TRIGGER': - return state.update('isTriggering', () => true); - case 'ON_TRIGGER_CANCELED': - return state - .update('isTriggering', () => false) - .update('triggerResponse', () => {}); - case 'ON_CHANGE': - return state.updateIn( - ['modifiedWebhook', ...action.keys], - () => action.value - ); - case 'ADD_NEW_HEADER': - return state.updateIn(['modifiedWebhook', ...action.keys], arr => - arr.push(fromJS({ key: '', value: '' })) - ); - case 'ON_HEADER_REMOVE': { - if (action.event === 'remove') { - return state.updateIn(['modifiedWebhook', 'headers'], headers => - headers.splice(action.index, 1) - ); - } else { - return state.updateIn( - ['modifiedWebhook', 'headers', action.index], - () => fromJS({ key: '', value: '' }) - ); - } - } - case 'RESET': - return state.update('modifiedWebhook', () => - fromJS(state.get('initialWebhook')) - ); - case 'SET_ERRORS': - return state.update('formErrors', () => fromJS(action.errors)); default: return state; } diff --git a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/fieldsRegex.js b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/fieldsRegex.js new file mode 100644 index 0000000000..3f08b72ffd --- /dev/null +++ b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/fieldsRegex.js @@ -0,0 +1,4 @@ +const NAME_REGEX = new RegExp('(^$)|(^[A-Za-z][_0-9A-Za-z ]*$)'); +const URL_REGEX = new RegExp('(^$)|((https?://.*)(d*)/?(.*))'); + +export { NAME_REGEX, URL_REGEX }; diff --git a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/form.js b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/form.js index 81b20cdf7a..adcdfe63f2 100644 --- a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/form.js +++ b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/form.js @@ -4,36 +4,24 @@ const form = { label: 'Name', type: 'text', value: '', - validations: { - required: true, - regex: new RegExp('(^$)|(^[A-Za-z][_0-9A-Za-z ]*$)'), - }, }, url: { styleName: 'col-12', label: 'URL', type: 'text', value: '', - validations: { - required: true, - regex: new RegExp('(^$)|((https?://.*)(d*)/?(.*))'), - }, }, headers: { styleName: 'col-12', label: 'Headers', type: 'headers', value: [{ key: '', value: '' }], - validations: {}, }, events: { styleName: 'col-12', label: 'Hooks', type: 'events', value: [], - validations: { - required: true, - }, }, }; diff --git a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/formatData.js b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/formatData.js new file mode 100644 index 0000000000..52dbf721e7 --- /dev/null +++ b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/formatData.js @@ -0,0 +1,40 @@ +import { set, setWith } from 'lodash'; + +const cleanData = data => { + const webhooks = data; + const headers = cleanHeaders(data.headers); + + set(webhooks, 'headers', unformatHeaders(headers)); + return webhooks; +}; + +const unformatHeaders = headers => { + return headers.reduce((obj, item) => { + const { key, value } = item; + return { + ...obj, + [key]: value, + }; + }, {}); +}; + +const cleanHeaders = headers => { + if (Object.keys(headers).length === 1) { + const { key, value } = headers[0]; + if (key.length === 0 && value.length === 0) { + return []; + } + } + return headers; +}; + +const cleanErrors = errors => { + return Object.keys(errors).reduce((acc, curr) => { + const { id } = errors[curr]; + setWith(acc, curr, id ? id : errors[curr], Object); + + return acc; + }, {}); +}; + +export { cleanHeaders, cleanData, cleanErrors }; diff --git a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/schema.js b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/schema.js index a143c1d4da..1d8b306199 100644 --- a/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/schema.js +++ b/packages/strapi-admin/admin/src/containers/Webhooks/EditView/utils/schema.js @@ -1,54 +1,31 @@ import * as yup from 'yup'; import { translatedErrors } from 'strapi-helper-plugin'; +import { NAME_REGEX, URL_REGEX } from './fieldsRegex'; -const createYupSchema = form => - yup.object().shape( - Object.keys(form).reduce((acc, current) => { - const { type, validations } = form[current]; - acc[current] = createYupSchemaEntry(type, validations); +const schema = yup.object().shape({ + name: yup + .string(translatedErrors.string) + .nullable() + .required(translatedErrors.required) + .matches(NAME_REGEX, translatedErrors.regex), + url: yup + .string(translatedErrors.string) + .nullable() + .required(translatedErrors.required) + .matches(URL_REGEX, translatedErrors.regex), + headers: yup + .array() + .of( + yup.object().shape({ + key: yup.string().required(), + value: yup.string().required(), + }) + ) + .nullable(), + events: yup + .array() + .nullable() + .required(translatedErrors.required), +}); - return acc; - }, {}) - ); - -const createYupSchemaEntry = (type, validations) => { - let schema = yup.mixed(); - - if (['text'].includes(type)) { - schema = yup.string(translatedErrors.string).nullable(); - } - - if (['headers'].includes(type)) { - schema = yup - .array() - .of( - yup.object().shape({ - key: yup.string().required(), - value: yup.string().required(), - }) - ) - .nullable(); - } - - if (['events'].includes(type)) { - schema = yup.array(); - } - - Object.keys(validations).forEach(validation => { - const validationValue = validations[validation]; - - switch (validation) { - case 'required': - schema = schema.required(translatedErrors.required); - break; - case 'regex': - schema = schema.matches(validationValue, translatedErrors.regex); - break; - default: - } - }); - - return schema; -}; - -export default createYupSchema; +export default schema; diff --git a/packages/strapi-admin/admin/src/containers/Webhooks/ListView/index.js b/packages/strapi-admin/admin/src/containers/Webhooks/ListView/index.js index 9b1c6140d7..07d2ac899b 100644 --- a/packages/strapi-admin/admin/src/containers/Webhooks/ListView/index.js +++ b/packages/strapi-admin/admin/src/containers/Webhooks/ListView/index.js @@ -26,25 +26,19 @@ import reducer, { initialState } from './reducer'; function ListView() { const { formatMessage } = useGlobalContext(); - const [webhooksToDelete, setWebhooksToDelete] = useState([]); const [showModal, setShowModal] = useState(false); const [reducerState, dispatch] = useReducer(reducer, initialState); const { push } = useHistory(); - const location = useLocation(); + const { pathname } = useLocation(); - const { shouldRefetchData, webhooks } = reducerState.toJS(); + const { webhooks, webhooksToDelete } = reducerState.toJS(); useEffect(() => { fetchData(); }, []); - useEffect(() => { - if (shouldRefetchData) { - fetchData(); - } - }, [shouldRefetchData]); - - const webhookIndex = id => webhooks.findIndex(webhook => webhook.id === id); + const getWebhookIndex = id => + webhooks.findIndex(webhook => webhook.id === id); const fetchData = async () => { try { @@ -70,7 +64,7 @@ function ListView() { const newButtonProps = { label: addBtnLabel, - onClick: () => handleCreateClick(), + onClick: () => handleGoTo('create'), color: 'primary', type: 'button', icon: , @@ -119,32 +113,27 @@ function ListView() { items: webhooks, }; - const handleCheckChange = (value, id) => { - if (value && !webhooksToDelete.includes(id)) { - setWebhooksToDelete([...webhooksToDelete, id]); - } + const handleChange = (value, id) => { + const updatedWebhooksToDelete = value + ? [...webhooksToDelete, id] + : webhooksToDelete.filter(webhookId => webhookId !== id); - if (!value && webhooksToDelete.includes(id)) { - setWebhooksToDelete([ - ...webhooksToDelete.filter(webhookId => webhookId !== id), - ]); - } + dispatch({ + type: 'SET_WEBHOOKS_TO_DELETE', + webhooks: updatedWebhooksToDelete, + }); }; - const handleCreateClick = () => { - push(`${location.pathname}/create`); + const handleGoTo = to => { + push(`${pathname}/${to}`); }; - const handleEditClick = id => { - push(`${location.pathname}/${id}`); - }; - - const handleDeleteAllConfirm = () => { - handleDeleteAllClick(); + const handleDeleteAllConfirm = async () => { + await onDeleteAllCLick(); setShowModal(false); }; - const handleDeleteAllClick = async () => { + const onDeleteAllCLick = async () => { const body = { ids: webhooksToDelete, }; @@ -158,8 +147,6 @@ function ListView() { dispatch({ type: 'WEBHOOKS_DELETED', }); - - setWebhooksToDelete([]); } catch (err) { if (err.code !== 20) { strapi.notification.error('notification.error'); @@ -167,11 +154,12 @@ function ListView() { } }; - const handleDeleteClick = id => { - deleteWebhook(id); + const handleDeleteConfirm = async id => { + await onDeleteCLick(id); + setShowModal(false); }; - const deleteWebhook = async id => { + const onDeleteCLick = async id => { try { await request(`/admin/webhooks/${id}`, { method: 'DELETE', @@ -179,7 +167,7 @@ function ListView() { dispatch({ type: 'WEBHOOK_DELETED', - index: webhookIndex(id), + index: getWebhookIndex(id), }); } catch (err) { if (err.code !== 20) { @@ -189,7 +177,10 @@ function ListView() { }; const handleEnabledChange = async (value, id) => { - const initialWebhookProps = webhooks[webhookIndex(id)]; + const webhookIndex = getWebhookIndex(id); + + const initialWebhookProps = webhooks[webhookIndex]; + const keys = [webhookIndex, 'isEnabled']; const body = { ...initialWebhookProps, @@ -199,17 +190,23 @@ function ListView() { delete body.id; try { + dispatch({ + type: 'SET_WEBHOOK_ENABLED', + keys, + value: value, + }); + await request(`/admin/webhooks/${id}`, { method: 'PUT', body, }); - + } catch (err) { dispatch({ type: 'SET_WEBHOOK_ENABLED', - keys: [webhookIndex(id), 'isEnabled'], - value: value, + keys, + value: !value, }); - } catch (err) { + if (err.code !== 20) { strapi.notification.error('notification.error'); } @@ -227,9 +224,9 @@ function ListView() { return ( diff --git a/packages/strapi-admin/admin/src/containers/Webhooks/ListView/reducer.js b/packages/strapi-admin/admin/src/containers/Webhooks/ListView/reducer.js index 206f07765a..5882e55107 100644 --- a/packages/strapi-admin/admin/src/containers/Webhooks/ListView/reducer.js +++ b/packages/strapi-admin/admin/src/containers/Webhooks/ListView/reducer.js @@ -2,23 +2,29 @@ import { fromJS } from 'immutable'; const initialState = fromJS({ webhooks: [], - shouldRefetchData: false, + webhooksToDelete: [], }); const reducer = (state, action) => { switch (action.type) { case 'GET_DATA_SUCCEEDED': - return state - .update('webhooks', () => fromJS(action.data)) - .update('shouldRefetchData', () => false); + return state.update('webhooks', () => fromJS(action.data)); case 'SET_WEBHOOK_ENABLED': return state.updateIn(['webhooks', ...action.keys], () => action.value); + case 'SET_WEBHOOKS_TO_DELETE': + return state.update('webhooksToDelete', () => action.webhooks); + case 'WEBHOOKS_DELETED': + return state + .update('webhooks', webhooks => + webhooks.filter(webhook => { + return !state.get('webhooksToDelete').includes(webhook.get('id')); + }) + ) + .update('webhooksToDelete', () => []); case 'WEBHOOK_DELETED': return state.update('webhooks', webhooks => webhooks.splice(action.index, 1) ); - case 'WEBHOOKS_DELETED': - return state.update('shouldRefetchData', v => !v); default: return state; } diff --git a/packages/strapi-admin/admin/src/translations/en.json b/packages/strapi-admin/admin/src/translations/en.json index 04323f3f6f..8c04daf086 100644 --- a/packages/strapi-admin/admin/src/translations/en.json +++ b/packages/strapi-admin/admin/src/translations/en.json @@ -221,6 +221,7 @@ "Auth.link.forgot-password": "Forgot your password?", "Auth.link.ready": "Ready to sign in?", "Settings.global": "Global Settings", + "Settings.error": "Error", "Settings.webhooks.title": "Webhooks", "Settings.webhooks.singular": "webhook", "Settings.webhooks.list.description": "Get POST changes notifications.", @@ -242,6 +243,7 @@ "Settings.webhooks.trigger.success": "Success!", "Settings.webhooks.trigger.success.label": "Trigger succeded", "Settings.webhooks.trigger.save": "Please save to trigger", + "Settings.webhooks.trigger.test": "Test-trigger", "Settings.webhooks.events.create": "Create", "Settings.webhooks.events.edit": "Edit", "Settings.webhooks.events.delete": "Delete", diff --git a/packages/strapi-plugin-content-manager/admin/src/assets/images/background_input.svg b/packages/strapi-plugin-content-manager/admin/src/assets/images/background_input.svg index 761aafc4e3..a50f054bf9 100644 --- a/packages/strapi-plugin-content-manager/admin/src/assets/images/background_input.svg +++ b/packages/strapi-plugin-content-manager/admin/src/assets/images/background_input.svg @@ -1,26 +1 @@ - - - - Group - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file