diff --git a/src/js/settings/components/classifai-registration/index.js b/src/js/settings/components/classifai-registration/index.js index e51f00b5e..aedb07680 100644 --- a/src/js/settings/components/classifai-registration/index.js +++ b/src/js/settings/components/classifai-registration/index.js @@ -174,7 +174,8 @@ export const SaveSettingsButton = ( { setSettings, onSaveSuccess = () => {}, } ) => { - const { createErrorNotice, removeNotices } = useDispatch( noticesStore ); + const { createErrorNotice, createSuccessNotice, removeNotices } = + useDispatch( noticesStore ); const notices = useSelect( ( select ) => select( noticesStore ).getNotices() ); @@ -200,11 +201,21 @@ export const SaveSettingsButton = ( { ); setSettings( res.settings ); setIsSaving( false ); + window.scrollTo( { + top: 0, + behavior: 'smooth', + } ); return; } setSettings( res.settings ); onSaveSuccess(); + createSuccessNotice( + __( 'Settings saved successfully.', 'classifai' ), + { + type: 'snackbar', + } + ); setIsSaving( false ); } ) .catch( ( error ) => { @@ -219,6 +230,10 @@ export const SaveSettingsButton = ( { } ); setIsSaving( false ); + window.scrollTo( { + top: 0, + behavior: 'smooth', + } ); } ); }; diff --git a/src/js/settings/components/classifai-settings/index.js b/src/js/settings/components/classifai-settings/index.js index bbbffe56c..f821ea4c9 100644 --- a/src/js/settings/components/classifai-settings/index.js +++ b/src/js/settings/components/classifai-settings/index.js @@ -14,11 +14,12 @@ import { /** * WordPress dependencies */ -import { useDispatch } from '@wordpress/data'; -import { SlotFillProvider } from '@wordpress/components'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { SlotFillProvider, SnackbarList } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { useEffect, useState } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; +import { store as noticeStore } from '@wordpress/notices'; /** * Internal dependencies @@ -28,6 +29,7 @@ import { STORE_NAME } from '../../data/store'; import { FeatureContext } from '../feature-settings/context'; import { ClassifAIRegistration } from '../classifai-registration'; import { ClassifAIWelcomeGuide } from './welcome-guide'; +import { Notices } from '../feature-settings/notices'; const { services, features } = window.classifAISettings; @@ -121,6 +123,43 @@ export const ServiceNavigation = () => { ); }; +/** + * Snackbar component to render the snackbar notifications. + * + * @return {React.ReactElement} The Snackbar component. + */ +export const SnackbarNotifications = () => { + const { removeNotice, removeNotices } = useDispatch( noticeStore ); + const location = useLocation(); + + const { notices } = useSelect( ( select ) => { + const allNotices = select( noticeStore ).getNotices(); + return { + notices: allNotices.filter( + ( notice ) => notice.type === 'snackbar' + ), + }; + }, [] ); + + useEffect( () => { + // Remove existing snackbar notices on location change. + if ( removeNotices ) { + removeNotices( notices.map( ( { id } ) => id ) ); + } else if ( removeNotice ) { + notices.forEach( ( { id } ) => removeNotice( id ) ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ location, removeNotice, removeNotices ] ); + + return ( + removeNotice( notice ) } + /> + ); +}; + /** * Main ClassifAI Settings Component. * @@ -188,6 +227,7 @@ export const ClassifAISettings = () => {
+ { />
+ ); diff --git a/src/js/settings/components/feature-settings/save-settings-button.js b/src/js/settings/components/feature-settings/save-settings-button.js index 185c68201..3f83e264b 100644 --- a/src/js/settings/components/feature-settings/save-settings-button.js +++ b/src/js/settings/components/feature-settings/save-settings-button.js @@ -33,8 +33,12 @@ export const SaveSettingsButton = ( { label = __( 'Save Settings', 'classifai' ), } ) => { const { featureName } = useFeatureSettings(); - const { createErrorNotice, removeNotices, removeNotice } = - useDispatch( noticesStore ); + const { + createSuccessNotice, + createErrorNotice, + removeNotices, + removeNotice, + } = useDispatch( noticesStore ); const notices = useSelect( ( select ) => select( noticesStore ).getNotices() ); @@ -79,9 +83,19 @@ export const SaveSettingsButton = ( { } ); setSettings( res.settings ); setIsSaving( false ); + window.scrollTo( { + top: 0, + behavior: 'smooth', + } ); return; } onSaveSuccess(); + createSuccessNotice( + __( 'Settings saved successfully.', 'classifai' ), + { + type: 'snackbar', + } + ); setSettings( res.settings ); setIsSaving( false ); } ) @@ -97,6 +111,10 @@ export const SaveSettingsButton = ( { } ); setIsSaving( false ); + window.scrollTo( { + top: 0, + behavior: 'smooth', + } ); } ); }; diff --git a/src/js/settings/components/service-settings/index.js b/src/js/settings/components/service-settings/index.js index f175f29db..3f41e9ce6 100644 --- a/src/js/settings/components/service-settings/index.js +++ b/src/js/settings/components/service-settings/index.js @@ -19,8 +19,9 @@ import { Notice, Icon, } from '@wordpress/components'; -import { useEffect, useRef } from '@wordpress/element'; +import { useEffect, useRef, useState } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -54,8 +55,18 @@ const ConfigureProviderNotice = () => ( * @return {Object} The ServiceSettings component. */ export const ServiceSettings = () => { + const [ enabled, setEnabled ] = useState( false ); const { setCurrentService, setIsSaving, setSettings } = useDispatch( STORE_NAME ); + const { + createSuccessNotice, + createErrorNotice, + removeNotices, + removeNotice, + } = useDispatch( noticesStore ); + const notices = useSelect( ( select ) => + select( noticesStore ).getNotices() + ); const { service } = useParams(); const isInitialPageLoad = useRef( true ); @@ -77,6 +88,13 @@ export const ServiceSettings = () => { const serviceFeatures = features[ service ] || {}; const saveSettings = () => { + // Remove existing notices. + if ( removeNotices ) { + removeNotices( notices.map( ( { id } ) => id ) ); + } else if ( removeNotice ) { + notices.forEach( ( { id } ) => removeNotice( id ) ); + } + setIsSaving( true ); apiFetch( { path: '/classifai/v1/settings/', @@ -86,15 +104,49 @@ export const ServiceSettings = () => { is_setup: true, step: 'enable_features', }, - } ).then( ( res ) => { - if ( res.errors && res.errors.length ) { - setIsSaving( false ); - return; - } + } ) + .then( ( res ) => { + if ( res.errors && res.errors.length ) { + res.errors.forEach( ( error ) => { + createErrorNotice( error.message, { + id: 'error-generic-notices', + } ); + } ); + setIsSaving( false ); + window.scrollTo( { + top: 0, + behavior: 'smooth', + } ); + return; + } - setSettings( res.settings ); - setIsSaving( false ); - } ); + const message = enabled + ? __( 'Feature enabled successfully.', 'classifai' ) + : __( 'Feature disabled successfully.', 'classifai' ); + + createSuccessNotice( message, { + type: 'snackbar', + } ); + setSettings( res.settings ); + setIsSaving( false ); + } ) + .catch( ( error ) => { + createErrorNotice( + error.message || + __( + 'An error occurred while saving settings.', + 'classifai' + ), + { + id: 'error-generic-notices', + } + ); + setIsSaving( false ); + window.scrollTo( { + top: 0, + behavior: 'smooth', + } ); + } ); }; const statuses = Object.keys( settings ) @@ -126,7 +178,8 @@ export const ServiceSettings = () => { feature ) } - onChange={ ( value ) => + onChange={ ( value ) => { + setEnabled( value ); wp.data .dispatch( STORE_NAME ) .setFeatureSettings( @@ -136,8 +189,8 @@ export const ServiceSettings = () => { : '0', }, feature - ) - } + ); + } } __nextHasNoMarginBottom /> diff --git a/src/scss/settings.scss b/src/scss/settings.scss index f13801452..35fe31414 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -430,6 +430,16 @@ .classifai-admin-notices { margin-right: 0px; } + + .classifai-settings-snackbar-notices { + position: fixed; + left: auto; + bottom: 40px; + + @media screen and (max-width: 600px) { + padding: 0px 20px 0px 0px; + } + } } .classifai-onboarding {