Skip to content

Commit

Permalink
Merge pull request #1866 from Codeinwp/fix/dashboard-options
Browse files Browse the repository at this point in the history
Improve Otter Dashboard UX
  • Loading branch information
HardeepAsrani authored Sep 25, 2023
2 parents b0ace38 + 73ef096 commit 2b0ba62
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 34 deletions.
8 changes: 6 additions & 2 deletions src/blocks/helpers/use-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ const useSettings = () => {
* @param {string?} success Success message for Notice.
* @param {string?} noticeId Notice ID.
* @param {function?} onSuccess Callback function to be executed on success.
* @param {function?} onError Callback function to be executed on error.
*/
const updateOption = ( option, value, success = __( 'Settings saved.', 'otter-blocks' ), noticeId = undefined, onSuccess = () => {}) => {
const updateOption = ( option, value, success = __( 'Settings saved.', 'otter-blocks' ), noticeId = undefined, onSuccess = () => {}, onError = () => {}) => {
setStatus( 'saving' );

const save = new api.models.Settings({ [option]: value }).save();
Expand Down Expand Up @@ -105,6 +106,7 @@ const useSettings = () => {
}
);
}

getSettings();
onSuccess?.( response );
});
Expand All @@ -114,13 +116,15 @@ const useSettings = () => {

createNotice(
'error',
response.responseJSON.message ? response.responseJSON.message : __( 'An unknown error occurred.', 'otter-blocks' ),
response?.responseJSON?.message ?? __( 'An unknown error occurred.', 'otter-blocks' ),
{
isDismissible: true,
type: 'snackbar',
id: noticeId
}
);

onError?.( response );
});
};

Expand Down
216 changes: 184 additions & 32 deletions src/dashboard/components/pages/Dashboard.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies.
*/
import { isString } from 'lodash';

/**
* WordPress dependencies.
*/
Expand All @@ -18,30 +23,131 @@ import { dispatch } from '@wordpress/data';
import {
Fragment,
useEffect,
useReducer,
useState
} from '@wordpress/element';

/**
* Internal dependencies.
*/
import ButtonControl from '../ButtonControl.js';
import useSettings from '../../../blocks/helpers/use-settings';

const optionMapping = {
enableCustomCss: 'themeisle_blocks_settings_css_module',
enableBlocksAnimation: 'themeisle_blocks_settings_blocks_animation',
enableBlockConditions: 'themeisle_blocks_settings_block_conditions',
enableSectionDefaultBlock: 'themeisle_blocks_settings_default_block',
enableOptimizeAnimationsCss: 'themeisle_blocks_settings_optimize_animations_css',
enableRichSchema: 'themeisle_blocks_settings_disable_review_schema',
enableReviewScale: 'themeisle_blocks_settings_review_scale',
enableHighlightDynamic: 'themeisle_blocks_settings_highlight_dynamic',
enableAnonymousDataTracking: 'otter_blocks_logger_flag'
};

const initialState = {
values: {
enableCustomCss: false,
enableBlocksAnimation: false,
enableBlockConditions: false,
enableSectionDefaultBlock: false,
enableOptimizeAnimationsCss: false,
enableRichSchema: false,
enableReviewScale: false,
enableHighlightDynamic: false,
enableAnonymousDataTracking: 'no'
},
status: {
enableCustomCss: 'init',
enableBlocksAnimation: 'init',
enableBlockConditions: 'init',
enableSectionDefaultBlock: 'init',
enableOptimizeAnimationsCss: 'init',
enableRichSchema: 'init',
enableReviewScale: 'init',
enableHighlightDynamic: 'init',
enableAnonymousDataTracking: 'init'
},
dirty: {
enableCustomCss: false,
enableBlocksAnimation: false,
enableBlockConditions: false,
enableSectionDefaultBlock: false,
enableOptimizeAnimationsCss: false,
enableRichSchema: false,
enableReviewScale: false,
enableHighlightDynamic: false,
enableAnonymousDataTracking: false
},
old: {}
};

/**
* Reducer.
* @param {Object} state The current state.
* @param {Object} action The action to be performed.
* @returns {*}
*/
const reducer = ( state, action ) => {
switch ( action.type ) {
case 'init':
state.values[ action.name ] = action.value;
state.status[ action.name ] = 'saved';
return { ...state };

case 'update':
state.old[ action.name ] = isString( state.values[ action.name ]) ? state.values[ action.name ] : Boolean( state.values[ action.name ]);
state.values[ action.name ] = action.value;
state.dirty[ action.name ] = true;
return { ...state };

case 'status_bulk':
action.names.forEach( name => {
state.status[ name ] = action.value;
state.dirty[ name ] = false;
});
return { ...state };

const Dashboard = ({
status,
getOption,
updateOption
}) => {
case 'saved':
state.status[ action.name ] = 'saved';
state.values[ action.name ] = action.value;
state.old[ action.name ] = undefined;
return { ...state };

case 'rollback':
if ( undefined !== state.old[ action.name ]) {
state.values[action.name] = state.old[action.name];
}
state.old[ action.name ] = undefined;
state.dirty[ action.name ] = false;
state.status[ action.name ] = 'saved';
return { ...state };

default:
return state;
}
};

const Dashboard = () => {
useEffect( () => {
if ( ! Boolean( window.otterObj.stylesExist ) ) {
setRegeneratedDisabled( true );
}
}, []);

const [ getOption, updateOption, status ] = useSettings();

const { createNotice } = dispatch( 'core/notices' );

const [ isRegeneratedDisabled, setRegeneratedDisabled ] = useState( false );
const [ isOpen, setOpen ] = useState( false );

const [ state, applyAction ] = useReducer( reducer, initialState );

/**
* Regenerate styles.
* @returns {Promise<void>}
*/
const regenerateStyles = async() => {
const data = await apiFetch({ path: 'otter/v1/regenerate', method: 'DELETE' });

Expand All @@ -58,6 +164,50 @@ const Dashboard = ({
setOpen( false );
};

/**
* Initialize the state with values from the WordPress options.
*/
useEffect( () => {
if ( 'loaded' !== status ) {
return;
}

Object.entries( state.status )
.filter( ([ key, value ]) => 'init' === value )
.forEach( ([ name, _ ]) => {
applyAction({ type: 'init', name, value: getOption( optionMapping[ name ]) });
});
}, [ state, status, getOption ]);

/**
* Update the WordPress options.
*/
useEffect( () => {
const dirtyOptionNames = Object.entries( state.dirty ).filter( ([ key, value ]) => value ).map( ([ key, value ]) => key );

if ( dirtyOptionNames.length ) {
if ( 'error' !== status ) {
applyAction({ type: 'status_bulk', value: 'saving', names: dirtyOptionNames });
}

for ( const name of dirtyOptionNames ) {
updateOption(
optionMapping[ name ],
state.values[ name ],
__( 'Settings saved.', 'otter-blocks' ),
'o-settings-saved-notice',
( response ) => {
applyAction({ type: 'saved', name, value: response[ optionMapping[ name ] ] });
},
() => {
applyAction({ type: 'rollback', name });
}
);
}
}

}, [ state, status ]);

return (
<Fragment>
<PanelBody
Expand All @@ -67,29 +217,31 @@ const Dashboard = ({
<ToggleControl
label={ __( 'Enable Custom CSS Module', 'otter-blocks' ) }
help={ __( 'Custom CSS module allows to add custom CSS to each block in Block Editor.', 'otter-blocks' ) }
checked={ Boolean( getOption( 'themeisle_blocks_settings_css_module' ) ) }
disabled={ 'saving' === status }
onChange={ () => updateOption( 'themeisle_blocks_settings_css_module', ! Boolean( getOption( 'themeisle_blocks_settings_css_module' ) ) ) }
checked={ state.values.enableCustomCss }
disabled={ 'saving' === state.status.enableCustomCss }
onChange={ ( value ) => {
applyAction({ type: 'update', name: 'enableCustomCss', value });
} }
/>
</PanelRow>

<PanelRow>
<ToggleControl
label={ __( 'Enable Blocks Animation Module', 'otter-blocks' ) }
help={ __( 'Blocks Animation module allows to add CSS animations to each block in Block Editor.', 'otter-blocks' ) }
checked={ Boolean( getOption( 'themeisle_blocks_settings_blocks_animation' ) ) }
disabled={ 'saving' === status }
onChange={ () => updateOption( 'themeisle_blocks_settings_blocks_animation', ! Boolean( getOption( 'themeisle_blocks_settings_blocks_animation' ) ) ) }
checked={ state.values.enableBlocksAnimation }
disabled={ 'saving' === state.status.enableBlocksAnimation }
onChange={ ( value ) => applyAction({ type: 'update', name: 'enableBlocksAnimation', value }) }
/>
</PanelRow>

<PanelRow>
<ToggleControl
label={ __( 'Enable Visibility Condition Module', 'otter-blocks' ) }
help={ __( 'Blocks Conditions module allows to hide/display blocks to your users based on selected conditions.', 'otter-blocks' ) }
checked={ Boolean( getOption( 'themeisle_blocks_settings_block_conditions' ) ) }
disabled={ 'saving' === status }
onChange={ () => updateOption( 'themeisle_blocks_settings_block_conditions', ! Boolean( getOption( 'themeisle_blocks_settings_block_conditions' ) ) ) }
checked={ state.values.enableBlockConditions }
disabled={ 'saving' === state.status.enableBlockConditions }
onChange={ ( value ) => applyAction({ type: 'update', name: 'enableBlockConditions', value }) }
/>
</PanelRow>
</PanelBody>
Expand All @@ -101,59 +253,59 @@ const Dashboard = ({
<ToggleControl
label={ __( 'Make Section your default block for Pages', 'otter-blocks' ) }
help={ __( 'Everytime you create a new page, Section block will be appended there by default.', 'otter-blocks' ) }
checked={ Boolean( getOption( 'themeisle_blocks_settings_default_block' ) ) }
disabled={ 'saving' === status }
onChange={ () => updateOption( 'themeisle_blocks_settings_default_block', ! Boolean( getOption( 'themeisle_blocks_settings_default_block' ) ) ) }
checked={ state.values.enableSectionDefaultBlock }
disabled={ 'saving' === state.status.enableSectionDefaultBlock }
onChange={ ( value ) => applyAction({ type: 'update', name: 'enableSectionDefaultBlock', value }) }
/>
</PanelRow>

<PanelRow>
<ToggleControl
label={ __( 'Optimize Animations CSS', 'otter-blocks' ) }
help={ __( 'Only load CSS for the animations that are used on the page. We recommend you to regenerate styles after you toggle this option.', 'otter-blocks' ) }
checked={ Boolean( getOption( 'themeisle_blocks_settings_optimize_animations_css' ) ) }
disabled={ 'saving' === status || ! Boolean( getOption( 'themeisle_blocks_settings_blocks_animation' ) ) }
onChange={ () => updateOption( 'themeisle_blocks_settings_optimize_animations_css', ! Boolean( getOption( 'themeisle_blocks_settings_optimize_animations_css' ) ) ) }
checked={ state.values.enableOptimizeAnimationsCss }
disabled={ 'saving' === state.status.enableOptimizeAnimationsCss }
onChange={ ( value ) => applyAction({ type: 'update', name: 'enableOptimizeAnimationsCss', value }) }
/>
</PanelRow>

<PanelRow>
<ToggleControl
label={ __( 'Enable Rich Schema', 'otter-blocks' ) }
help={ __( 'Control if you want to show rich schema in Product Review Block.', 'otter-blocks' ) }
checked={ Boolean( getOption( 'themeisle_blocks_settings_disable_review_schema' ) ) }
disabled={ 'saving' === status }
onChange={ () => updateOption( 'themeisle_blocks_settings_disable_review_schema', ! Boolean( getOption( 'themeisle_blocks_settings_disable_review_schema' ) ) ) }
checked={ state.values.enableRichSchema }
disabled={ 'saving' === state.status.enableRichSchema }
onChange={ ( value ) => applyAction({ type: 'update', name: 'enableRichSchema', value }) }
/>
</PanelRow>

<PanelRow>
<ToggleControl
label={ __( 'Use 1-5 Scale for Review Block', 'otter-blocks' ) }
help={ __( 'Use 1-5 rating scale instead of the default 1-10.', 'otter-blocks' ) }
checked={ Boolean( getOption( 'themeisle_blocks_settings_review_scale' ) ) }
disabled={ 'saving' === status }
onChange={ () => updateOption( 'themeisle_blocks_settings_review_scale', ! Boolean( getOption( 'themeisle_blocks_settings_review_scale' ) ) ) }
checked={ state.values.enableReviewScale }
disabled={ 'saving' === state.status.enableReviewScale }
onChange={ ( value ) => applyAction({ type: 'update', name: 'enableReviewScale', value }) }
/>
</PanelRow>

<PanelRow>
<ToggleControl
label={ __( 'Highlight the Dynamic Text', 'otter-blocks' ) }
help={ __( 'Easily differentiate between dynamic and normal text in the editor.', 'otter-blocks' ) }
checked={ Boolean( getOption( 'themeisle_blocks_settings_highlight_dynamic' ) ) }
disabled={ 'saving' === status }
onChange={ () => updateOption( 'themeisle_blocks_settings_highlight_dynamic', ! Boolean( getOption( 'themeisle_blocks_settings_highlight_dynamic' ) ) ) }
checked={ state.values.enableHighlightDynamic }
disabled={ 'saving' === state.status.enableHighlightDynamic }
onChange={ ( value ) => applyAction({ type: 'update', name: 'enableHighlightDynamic', value }) }
/>
</PanelRow>

<PanelRow>
<ToggleControl
label={ __( 'Anonymous Data Tracking.', 'otter-blocks' ) }
help={ __( 'Become a contributor by opting in to our anonymous data tracking. We guarantee no sensitive data is collected.', 'otter-blocks' ) }
checked={ 'yes' === getOption( 'otter_blocks_logger_flag' ) ? true : false }
disabled={ 'saving' === status }
onChange={ () => updateOption( 'otter_blocks_logger_flag', ( 'yes' === getOption( 'otter_blocks_logger_flag' ) ? 'no' : 'yes' ) ) }
checked={ 'yes' === state.values.enableAnonymousDataTracking }
disabled={ 'saving' === state.status.enableAnonymousDataTracking }
onChange={ ( value ) => applyAction({ type: 'update', name: 'enableAnonymousDataTracking', value: value ? 'yes' : 'no' }) }
/>
</PanelRow>
</PanelBody>
Expand Down

0 comments on commit 2b0ba62

Please sign in to comment.