Allow option editing
This commit is contained in:
parent
f0098cdbcb
commit
028d4401f2
|
@ -22,7 +22,8 @@
|
|||
"devDependencies": {
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "1.4.0",
|
||||
"@woocommerce/eslint-plugin": "1.1.0",
|
||||
"@wordpress/scripts": "^13.0.3"
|
||||
"@wordpress/scripts": "^13.0.3",
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@woocommerce/data": "^1.1.1",
|
||||
|
|
|
@ -23,4 +23,21 @@
|
|||
background-color: #dc3545;
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.components-notice {
|
||||
margin: 0px 0px 10px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.wca-test-helper-option-editor {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.wca-test-helper-edit-btn-save {
|
||||
float: right;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEffect, useState } from '@wordpress/element';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from '@wordpress/components';
|
||||
|
||||
const OptionEditor = ( props ) => {
|
||||
const [ value, setValue ] = useState( props.option.content );
|
||||
|
||||
useEffect( () => {
|
||||
setValue( props.option.content );
|
||||
}, [ props.option ] );
|
||||
|
||||
const handleChange = ( event ) => {
|
||||
setValue( event.target.value );
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
props.onSave( props.option.name, value );
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<textarea
|
||||
className="wca-test-helper-option-editor"
|
||||
value={ value }
|
||||
onChange={ handleChange }
|
||||
></textarea>
|
||||
<Button
|
||||
className="wca-test-helper-edit-btn-save"
|
||||
isPrimary
|
||||
onClick={ handleSave }
|
||||
disabled={ props.option.isSaving === true }
|
||||
>
|
||||
{ props.option.isSaving ? 'Saving...' : 'Save' }
|
||||
</Button>
|
||||
<div className="clear"></div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
OptionEditor.propTypes = {
|
||||
option: PropTypes.object.isRequired,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default OptionEditor;
|
|
@ -1,6 +1,8 @@
|
|||
const TYPES = {
|
||||
SET_OPTIONS: 'SET_OPTIONS',
|
||||
SET_OPTION_FOR_EDITING: 'SET_OPTION_FOR_EDITING',
|
||||
SET_IS_LOADING: 'SET_IS_LOADING',
|
||||
SET_NOTICE: 'SET_NOTICE',
|
||||
DELETE_OPTION_BY_ID: 'DELETE_OPTION_BY_ID',
|
||||
};
|
||||
|
||||
|
|
|
@ -28,6 +28,20 @@ export function setLoadingState( isLoading ) {
|
|||
};
|
||||
}
|
||||
|
||||
export function setOptionForEditing( editingOption ) {
|
||||
return {
|
||||
type: TYPES.SET_OPTION_FOR_EDITING,
|
||||
editingOption,
|
||||
};
|
||||
}
|
||||
|
||||
export function setNotice( notice ) {
|
||||
return {
|
||||
type: TYPES.SET_NOTICE,
|
||||
notice,
|
||||
};
|
||||
}
|
||||
|
||||
export function* deleteOptionById( optionId ) {
|
||||
try {
|
||||
yield apiFetch( {
|
||||
|
@ -42,3 +56,26 @@ export function* deleteOptionById( optionId ) {
|
|||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
export function* saveOption( optionName, newOptionValue ) {
|
||||
try {
|
||||
const payload = {};
|
||||
payload[ optionName ] = JSON.parse( newOptionValue );
|
||||
yield apiFetch( {
|
||||
method: 'POST',
|
||||
path: '/wc-admin/options',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify( payload ),
|
||||
} );
|
||||
yield setNotice( {
|
||||
status: 'success',
|
||||
message: optionName + ' has been saved.',
|
||||
} );
|
||||
} catch {
|
||||
yield setNotice( {
|
||||
status: 'error',
|
||||
message: 'Unable to save ' + optionName,
|
||||
} );
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,26 @@ import TYPES from './action-types';
|
|||
const DEFAULT_STATE = {
|
||||
options: [],
|
||||
isLoading: true,
|
||||
editingOption: {
|
||||
name: null,
|
||||
content: '{}',
|
||||
},
|
||||
notice: {
|
||||
status: 'success',
|
||||
message: '',
|
||||
},
|
||||
};
|
||||
|
||||
const reducer = ( state = DEFAULT_STATE, action ) => {
|
||||
switch ( action.type ) {
|
||||
case TYPES.SET_OPTION_FOR_EDITING:
|
||||
return {
|
||||
...state,
|
||||
editingOption: {
|
||||
...state.editingOption,
|
||||
...action.editingOption,
|
||||
},
|
||||
};
|
||||
case TYPES.SET_IS_LOADING:
|
||||
return {
|
||||
...state,
|
||||
|
@ -21,6 +37,14 @@ const reducer = ( state = DEFAULT_STATE, action ) => {
|
|||
options: action.options,
|
||||
isLoading: false,
|
||||
};
|
||||
case TYPES.SET_NOTICE:
|
||||
return {
|
||||
...state,
|
||||
notice: {
|
||||
...state.notice,
|
||||
...action.notice,
|
||||
},
|
||||
};
|
||||
case TYPES.DELETE_OPTION_BY_ID:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -7,7 +7,7 @@ import { apiFetch } from '@wordpress/data-controls';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { API_NAMESPACE } from './constants';
|
||||
import { setLoadingState, setOptions } from './actions';
|
||||
import { setLoadingState, setOptions, setOptionForEditing } from './actions';
|
||||
|
||||
export function* getOptions( search ) {
|
||||
let path = `${ API_NAMESPACE }/options?`;
|
||||
|
@ -26,3 +26,36 @@ export function* getOptions( search ) {
|
|||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
export function* getOptionForEditing( optionName ) {
|
||||
const loadingOption = {
|
||||
name: 'Loading...',
|
||||
content: '',
|
||||
saved: false,
|
||||
};
|
||||
if ( optionName === undefined ) {
|
||||
return setOptionForEditing( loadingOption );
|
||||
}
|
||||
|
||||
yield setOptionForEditing( loadingOption );
|
||||
|
||||
const path = '/wc-admin/options?options=' + optionName;
|
||||
|
||||
try {
|
||||
const response = yield apiFetch( {
|
||||
path,
|
||||
} );
|
||||
|
||||
let content = response[ optionName ];
|
||||
if ( typeof content === 'object' ) {
|
||||
content = JSON.stringify( response[ optionName ], null, 2 );
|
||||
}
|
||||
|
||||
yield setOptionForEditing( {
|
||||
name: optionName,
|
||||
content,
|
||||
} );
|
||||
} catch ( error ) {
|
||||
throw new Error( error );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,3 +5,11 @@ export function getOptions( state ) {
|
|||
export function isLoading( state ) {
|
||||
return state.isLoading;
|
||||
}
|
||||
|
||||
export function getOptionForEditing( state ) {
|
||||
return state.editingOption;
|
||||
}
|
||||
|
||||
export function getNotice( state ) {
|
||||
return state.notice;
|
||||
}
|
||||
|
|
|
@ -3,11 +3,14 @@
|
|||
*/
|
||||
import { withDispatch, withSelect } from '@wordpress/data';
|
||||
import { compose } from '@wordpress/compose';
|
||||
import { Modal, Notice } from '@wordpress/components';
|
||||
import { useState } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_KEY } from './data/constants';
|
||||
import { default as OptionEditor } from './OptionEditor';
|
||||
import './data';
|
||||
|
||||
function Options( {
|
||||
|
@ -16,29 +19,50 @@ function Options( {
|
|||
deleteOptionById,
|
||||
isLoading,
|
||||
invalidateResolution,
|
||||
getOptionForEditing,
|
||||
editingOption,
|
||||
saveOption,
|
||||
notice,
|
||||
setNotice,
|
||||
} ) {
|
||||
const deleteOption = function ( optionId ) {
|
||||
const [ isEditModalOpen, setEditModalOpen ] = useState( false );
|
||||
|
||||
const deleteOption = ( optionId ) => {
|
||||
// eslint-disable-next-line no-alert
|
||||
if ( confirm( 'Are you sure you want to delete?' ) ) {
|
||||
deleteOptionById( optionId );
|
||||
}
|
||||
};
|
||||
|
||||
const renderLoading = function () {
|
||||
const openEditModal = ( optionName ) => {
|
||||
invalidateResolution( STORE_KEY, 'getOptionForEditing', [
|
||||
optionName,
|
||||
] );
|
||||
|
||||
getOptionForEditing( optionName );
|
||||
setEditModalOpen( true );
|
||||
};
|
||||
|
||||
const handleSaveOption = ( optionName, newValue ) => {
|
||||
saveOption( optionName, newValue );
|
||||
setEditModalOpen( false );
|
||||
};
|
||||
|
||||
const renderLoading = () => {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan="4" align="center">
|
||||
<td colSpan="5" align="center">
|
||||
Loading...
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTableData = function () {
|
||||
const renderTableData = () => {
|
||||
if ( options.length === 0 ) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan="4" align="center">
|
||||
<td colSpan="5" align="center">
|
||||
No Options Found
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -58,8 +82,10 @@ function Options( {
|
|||
<tr key={ optionId }>
|
||||
<td key={ 0 }>{ optionId }</td>
|
||||
<td key={ 1 }>{ optionName }</td>
|
||||
<td key={ 2 }>{ autoload }</td>
|
||||
<td key={ 3 }>
|
||||
<td className="align-center" key={ 2 }>
|
||||
{ autoload }
|
||||
</td>
|
||||
<td className="align-center" key={ 3 }>
|
||||
<button
|
||||
className="button btn-danger"
|
||||
onClick={ () => deleteOption( optionId ) }
|
||||
|
@ -67,12 +93,20 @@ function Options( {
|
|||
Delete
|
||||
</button>
|
||||
</td>
|
||||
<td className="align-center" key={ 4 }>
|
||||
<button
|
||||
className="button btn-primary"
|
||||
onClick={ () => openEditModal( optionName ) }
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
} );
|
||||
};
|
||||
|
||||
const searchOption = function ( event ) {
|
||||
const searchOption = ( event ) => {
|
||||
event.preventDefault();
|
||||
const keyword = event.target.search.value;
|
||||
|
||||
|
@ -84,61 +118,125 @@ function Options( {
|
|||
};
|
||||
|
||||
return (
|
||||
<div id="wc-admin-test-helper-options">
|
||||
<form onSubmit={ searchOption }>
|
||||
<div className="search-box">
|
||||
<label
|
||||
className="screen-reader-text"
|
||||
htmlFor="post-search-input"
|
||||
<>
|
||||
{ isEditModalOpen && (
|
||||
<Modal
|
||||
title={ editingOption.name }
|
||||
onRequestClose={ () => {
|
||||
setEditModalOpen( false );
|
||||
} }
|
||||
>
|
||||
<OptionEditor
|
||||
option={ editingOption }
|
||||
onSave={ handleSaveOption }
|
||||
></OptionEditor>
|
||||
</Modal>
|
||||
) }
|
||||
<div id="wc-admin-test-helper-options">
|
||||
{ notice.message.length > 0 && (
|
||||
<Notice
|
||||
status={ notice.status }
|
||||
onRemove={ () => {
|
||||
setNotice( { message: '' } );
|
||||
} }
|
||||
>
|
||||
Search products:
|
||||
</label>
|
||||
<input type="search" name="search" />
|
||||
<input
|
||||
type="submit"
|
||||
id="search-submit"
|
||||
className="button"
|
||||
value="Search Option"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div className="clear"></div>
|
||||
<table className="wp-list-table striped table-view-list widefat">
|
||||
<thead>
|
||||
<tr>
|
||||
<td className="manage-column column-thumb" key={ 0 }>
|
||||
I.D
|
||||
</td>
|
||||
<td className="manage-column column-thumb" key={ 1 }>
|
||||
Name
|
||||
</td>
|
||||
<td className="manage-column column-thumb" key={ 2 }>
|
||||
Autoload
|
||||
</td>
|
||||
<td className="manage-column column-thumb" key={ 3 }>
|
||||
Delete
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ isLoading ? renderLoading() : renderTableData() }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{ notice.message }
|
||||
</Notice>
|
||||
) }
|
||||
<form onSubmit={ searchOption }>
|
||||
<div className="search-box">
|
||||
<label
|
||||
className="screen-reader-text"
|
||||
htmlFor="post-search-input"
|
||||
>
|
||||
Search products:
|
||||
</label>
|
||||
<input type="search" name="search" />
|
||||
<input
|
||||
type="submit"
|
||||
id="search-submit"
|
||||
className="button"
|
||||
value="Search Option"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
<div className="clear"></div>
|
||||
<table className="wp-list-table striped table-view-list widefat">
|
||||
<thead>
|
||||
<tr>
|
||||
<td
|
||||
className="manage-column column-thumb"
|
||||
key={ 0 }
|
||||
>
|
||||
I.D
|
||||
</td>
|
||||
<td
|
||||
className="manage-column column-thumb"
|
||||
key={ 1 }
|
||||
>
|
||||
Name
|
||||
</td>
|
||||
<td
|
||||
className="manage-column column-thumb align-center"
|
||||
key={ 2 }
|
||||
>
|
||||
Autoload
|
||||
</td>
|
||||
<td
|
||||
className="manage-column column-thumb align-center"
|
||||
key={ 3 }
|
||||
>
|
||||
Delete
|
||||
</td>
|
||||
<td
|
||||
className="manage-column column-thumb align-center"
|
||||
key={ 4 }
|
||||
>
|
||||
Edit
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ isLoading ? renderLoading() : renderTableData() }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withSelect( ( select ) => {
|
||||
const { getOptions, isLoading } = select( STORE_KEY );
|
||||
const {
|
||||
getOptions,
|
||||
getOptionForEditing,
|
||||
getNotice,
|
||||
isLoading,
|
||||
} = select( STORE_KEY );
|
||||
const options = getOptions();
|
||||
const editingOption = getOptionForEditing();
|
||||
const notice = getNotice();
|
||||
|
||||
return { options, getOptions, isLoading: isLoading() };
|
||||
return {
|
||||
options,
|
||||
getOptions,
|
||||
isLoading: isLoading(),
|
||||
editingOption,
|
||||
getOptionForEditing,
|
||||
notice,
|
||||
};
|
||||
} ),
|
||||
withDispatch( ( dispatch ) => {
|
||||
const { deleteOptionById } = dispatch( STORE_KEY );
|
||||
const { deleteOptionById, saveOption, setNotice } = dispatch(
|
||||
STORE_KEY
|
||||
);
|
||||
const { invalidateResolution } = dispatch( 'core/data' );
|
||||
|
||||
return { deleteOptionById, invalidateResolution };
|
||||
return {
|
||||
deleteOptionById,
|
||||
invalidateResolution,
|
||||
saveOption,
|
||||
setNotice,
|
||||
};
|
||||
} )
|
||||
)( Options );
|
||||
|
|
Loading…
Reference in New Issue