Skip to content

Commit

Permalink
Merge pull request #3078 from woocommerce/PCP-4138-confirmation-messa…
Browse files Browse the repository at this point in the history
…ge-after-saving-settings

Confirmation message after saving settings (4138)
  • Loading branch information
Dinamiko authored Feb 7, 2025
2 parents c35b0db + 877c7bb commit b1adebd
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 101 deletions.
4 changes: 4 additions & 0 deletions modules/ppcp-settings/resources/css/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ $card-vertical-gap: 48px;
--color-gray-200: #{$color-gray-200};
--color-gray-100: #{$color-gray-100};
--color-gradient-dark: #{$color-gradient-dark};
--color-success-background: #DAFFE0;
--color-success-text: #144722;
--color-failure-background: #faeded;
--color-failure-text: #5c0000;

--color-preview-background: #FAF8F5;
--color-separators: #{$color-gray-200};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,33 @@ $margin_bottom: 48px;
}
}
}

.ppcp-r-navbar-notice {
// Theming.
background: var(--navbar-notice-background);
color: var(--navbar-notice-color);

// Animation.
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
opacity: 0;
transform: translateY(-10px);

&.ppcp--animating {
opacity: 1;
transform: translateY(0);
}

// Styling.
position: absolute;
top: 48px;
right: 0;
white-space: nowrap;
padding: 6px 12px;
border-radius: 3px;

&.ppcp--success {
--navbar-notice-background: var(--color-success-background);
--navbar-notice-color: var(--color-success-text);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { Button } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';

import TopNavigation from '../../../ReusableComponents/TopNavigation';
import { useSaveSettings } from '../../../../hooks/useSaveSettings';
import { CommonHooks } from '../../../../data';
import TabBar from '../../../ReusableComponents/TabBar';
import classNames from 'classnames';

// How long the save confirmation stays visible, in milliseconds.
const SAVE_CONFIRMATION_DURATION = 2500;

// How long does the CSS transition last (match this with _navigation.scss values)
const NOTIFICATION_ANIMATION_DURATION = 300;

const SettingsNavigation = ( {
canSave = true,
Expand All @@ -28,12 +37,75 @@ const SettingsNavigation = ( {
}
>
{ canSave && (
<Button variant="primary" onClick={ persistAll }>
{ __( 'Save', 'woocommerce-paypal-payments' ) }
</Button>
<>
<Button variant="primary" onClick={ persistAll }>
{ __( 'Save', 'woocommerce-paypal-payments' ) }
</Button>
<SaveStateMessage />
</>
) }
</TopNavigation>
);
};

export default SettingsNavigation;

const SaveStateMessage = () => {
const [ isSaving, setIsSaving ] = useState( false );
const [ isVisible, setIsVisible ] = useState( false );
const [ isAnimating, setIsAnimating ] = useState( false );
const { onStarted, onFinished } = CommonHooks.useActivityObserver();
const timerRef = useRef( null );

const handleActivityStart = useCallback( ( started ) => {
if ( started.startsWith( 'persist' ) ) {
setIsSaving( true );
setIsVisible( false );
setIsAnimating( false );

if ( timerRef.current ) {
clearTimeout( timerRef.current );
}
}
}, [] );

const handleActivityDone = useCallback(
( done, remaining ) => {
if ( isSaving && remaining.length === 0 ) {
setIsSaving( false );
setIsVisible( true );
setTimeout( () => setIsAnimating( true ), 50 );

timerRef.current = setTimeout( () => {
setIsAnimating( false );
setTimeout(
() => setIsVisible( false ),
NOTIFICATION_ANIMATION_DURATION
);
}, SAVE_CONFIRMATION_DURATION );
}
},
[ isSaving ]
);

useEffect( () => {
onStarted( handleActivityStart );
onFinished( handleActivityDone );
}, [ onStarted, onFinished, handleActivityStart, handleActivityDone ] );

if ( ! isVisible ) {
return null;
}

const className = classNames( 'ppcp-r-navbar-notice', 'ppcp--success', {
'ppcp--animating': isAnimating,
} );

return (
<span className={ className }>
<span className="ppcp--inner-text">
{ __( 'Completed', 'woocommerce-paypal-payments' ) }
</span>
</span>
);
};
16 changes: 5 additions & 11 deletions modules/ppcp-settings/resources/js/data/_example/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,10 @@ export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
*/
export function persist() {
return async ( { select } ) => {
const data = select.persistentData();

try {
await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data,
} );
} catch ( e ) {
console.error( 'Error saving progress.', e );
}
await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data: select.persistentData(),
} );
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@ import {
*/
export function persist() {
return async ( { select } ) => {
const data = select.persistentData();

await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data,
data: select.persistentData(),
} );
};
}
Expand Down
54 changes: 52 additions & 2 deletions modules/ppcp-settings/resources/js/data/common/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import { useDispatch, useSelect } from '@wordpress/data';
import { useCallback } from '@wordpress/element';
import { useCallback, useEffect, useState } from '@wordpress/element';

import { createHooksForStore } from '../utils';
import { STORE_NAME } from './constants';
Expand Down Expand Up @@ -194,6 +194,8 @@ export const useBusyState = () => {
const withActivity = useCallback(
async ( id, description, asyncFn ) => {
startActivity( id, description );

// Intentionally does not catch errors but propagates them to the calling module.
try {
return await asyncFn();
} finally {
Expand All @@ -206,6 +208,54 @@ export const useBusyState = () => {
return {
withActivity, // HOC
isBusy, // Boolean.
activities, // Object.
};
};

export const useActivityObserver = () => {
const activities = useSelect(
( select ) => select( STORE_NAME ).getActivityList(),
[]
);

const [ prevActivities, setPrevActivities ] = useState( activities );

useEffect( () => {
setPrevActivities( activities );
}, [ activities ] );

const onStarted = useCallback(
( callback ) => {
const newActivities = Object.keys( activities ).filter(
( id ) => ! prevActivities[ id ]
);
if ( ! newActivities.length ) {
return;
}
newActivities.forEach( ( id ) =>
callback( id, Object.keys( activities ) )
);
},
[ activities, prevActivities ]
);

const onFinished = useCallback(
( callback ) => {
const finishedActivities = Object.keys( prevActivities ).filter(
( id ) => ! activities[ id ]
);
if ( ! finishedActivities.length ) {
return;
}
finishedActivities.forEach( ( id ) =>
callback( id, Object.keys( activities ) )
);
},
[ activities, prevActivities ]
);

return {
activities,
onStarted,
onFinished,
};
};
5 changes: 2 additions & 3 deletions modules/ppcp-settings/resources/js/data/onboarding/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,14 @@ export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
*/
export function persist() {
return async ( { select } ) => {
const data = select.persistentData();

try {
await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data,
data: select.persistentData(),
} );
} catch ( e ) {
// We catch errors here, as the onboarding module is not handled by the persistAll hook.
console.error( 'Error saving progress.', e );
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,10 @@ export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
*/
export function persist() {
return async ( { select } ) => {
const data = select.persistentData();

try {
await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data,
} );
} catch ( e ) {
console.error( 'Error saving progress.', e );
}
await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data: select.persistentData(),
} );
};
}
16 changes: 5 additions & 11 deletions modules/ppcp-settings/resources/js/data/payment/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,10 @@ export const changePaymentSettings = ( id, props ) => ( {
*/
export function persist() {
return async ( { select } ) => {
const data = select.persistentData();

try {
await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data,
} );
} catch ( e ) {
console.error( 'Error saving progress.', e );
}
await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data: select.persistentData(),
} );
};
}
16 changes: 5 additions & 11 deletions modules/ppcp-settings/resources/js/data/settings/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,10 @@ export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );
*/
export function persist() {
return async ( { select } ) => {
const data = select.persistentData();

try {
await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data,
} );
} catch ( e ) {
console.error( 'Error saving progress.', e );
}
await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data: select.persistentData(),
} );
};
}
20 changes: 7 additions & 13 deletions modules/ppcp-settings/resources/js/data/styling/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@ export const setPersistent = ( prop, value ) => ( {
/**
* Transient. Changes the "ready-state" of the module.
*
* @param {boolean} state Whether the store is ready to be used.
* @param {boolean} isReady Whether the store is ready to be used.
* @return {Action} The action.
*/
export const setIsReady = ( state ) => setTransient( 'isReady', state );
export const setIsReady = ( isReady ) => setTransient( 'isReady', isReady );

/**
* Thunk action creator. Triggers the persistence of store data to the server.
Expand All @@ -75,16 +75,10 @@ export const setIsReady = ( state ) => setTransient( 'isReady', state );
*/
export function persist() {
return async ( { select } ) => {
const data = select.persistentData();

try {
await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data,
} );
} catch ( e ) {
console.error( 'Error saving progress.', e );
}
await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data: select.persistentData(),
} );
};
}
3 changes: 1 addition & 2 deletions modules/ppcp-settings/resources/js/data/todos/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,10 @@ export function fetchTodos() {

export function persist() {
return async ( { select } ) => {
const data = await select.persistentData();
return await apiFetch( {
path: REST_PERSIST_PATH,
method: 'POST',
data,
data: select.persistentData(),
} );
};
}
Expand Down
Loading

0 comments on commit b1adebd

Please sign in to comment.