diff --git a/.gitignore b/.gitignore
index 7aff58b919..de256cf9e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@ Thumbs.db
/vendor/
# Built files
+/build
/dist
/packages/components/colors
/packages/components/dist
diff --git a/src/wizards/newsletters/views/settings/index.js b/src/wizards/newsletters/views/settings/index.js
index 1f9f2db880..df6dcec552 100644
--- a/src/wizards/newsletters/views/settings/index.js
+++ b/src/wizards/newsletters/views/settings/index.js
@@ -11,10 +11,33 @@ import once from 'lodash/once';
/**
* WordPress dependencies
*/
-import { useEffect, useState, Fragment } from '@wordpress/element';
+import { useEffect, useRef, useState, Fragment } from '@wordpress/element';
import apiFetch from '@wordpress/api-fetch';
import { sprintf, __ } from '@wordpress/i18n';
-import { CheckboxControl, TextareaControl, ExternalLink, Notice } from '@wordpress/components';
+import {
+ CheckboxControl,
+ ExternalLink,
+ Notice,
+ __experimentalHStack as HStack, // eslint-disable-line @wordpress/no-unsafe-wp-apis
+} from '@wordpress/components';
+
+// Wizard-bridge events. Mirror of `newspack-newsletters/src/wizard-bridge/events.js`
+// — kept locally so this file is self-contained without a cross-repo import.
+const NN_EVENT_NAMESPACE = 'newspack-newsletters';
+const NN_EVENTS = {
+ BRIDGE_MOUNTED: `${ NN_EVENT_NAMESPACE }:bridge-mounted`,
+ OPEN_MODAL: `${ NN_EVENT_NAMESPACE }:open-local-list-modal`,
+ OPEN_CONFIRM_DELETE: `${ NN_EVENT_NAMESPACE }:open-local-list-confirm-delete`,
+ LOCAL_LIST_SAVED: `${ NN_EVENT_NAMESPACE }:local-list-saved`,
+ LOCAL_LIST_DELETED: `${ NN_EVENT_NAMESPACE }:local-list-deleted`,
+};
+const NN_FALLBACK_TIMEOUT_MS = 500;
+
+// Read the bridge-readiness flag synchronously rather than relying on a
+// one-shot `BRIDGE_MOUNTED` event. The bridge sets the flag before
+// dispatching, so listeners that register late still observe a ready
+// bridge — avoiding a spurious fallback redirect.
+const isBridgeReady = () => typeof window !== 'undefined' && window.newspackNewslettersBridgeReady === true;
/**
* Internal dependencies
@@ -251,7 +274,10 @@ export const Settings = ( {
export const SubscriptionLists = ( { lockedLists, onUpdate, provider } ) => {
const [ error, setError ] = useState( false );
const [ inFlight, setInFlight ] = useState( false );
+ const [ togglingId, setTogglingId ] = useState( null );
const [ lists, setLists ] = useState( [] );
+ const fallbackTimerRef = useRef( null );
+
const updateConfig = data => {
setLists( data );
if ( typeof onUpdate === 'function' ) {
@@ -268,37 +294,75 @@ export const SubscriptionLists = ( { lockedLists, onUpdate, provider } ) => {
.catch( setError )
.finally( () => setInFlight( false ) );
};
- const saveLists = () => {
+ const handleToggleActive = async ( list, next ) => {
+ if ( ! list?.db_id ) {
+ return;
+ }
+ const snapshot = lists;
+ updateConfig( lists.map( row => ( row.db_id === list.db_id ? { ...row, active: next } : row ) ) );
+ setTogglingId( list.db_id );
setError( false );
- setInFlight( true );
- apiFetch( {
- path: '/newspack-newsletters/v1/lists',
- method: 'post',
- data: { lists },
- } )
- .then( updateConfig )
- .catch( setError )
- .finally( () => setInFlight( false ) );
- };
- const handleChange = ( index, name ) => value => {
- const newLists = [ ...lists ];
- newLists[ index ][ name ] = value;
- updateConfig( newLists );
+ try {
+ const response = await apiFetch( {
+ path: `/newspack-newsletters/v1/lists/${ list.db_id }`,
+ method: 'PATCH',
+ data: { active: next },
+ } );
+ updateConfig( lists.map( row => ( row.db_id === list.db_id ? { ...row, ...response } : row ) ) );
+ } catch ( err ) {
+ updateConfig( snapshot );
+ setError( err );
+ } finally {
+ setTogglingId( null );
+ }
};
- // Handle provider updates.
+
useEffect( () => {
setError( false );
if ( provider && ! lockedLists ) {
- // Empty lists before fetching to prevent previous list from appearing while fetching.
setLists( [] );
fetchLists();
}
}, [ provider, lockedLists ] );
+ useEffect( () => {
+ const reload = () => fetchLists();
+ document.addEventListener( NN_EVENTS.LOCAL_LIST_SAVED, reload );
+ document.addEventListener( NN_EVENTS.LOCAL_LIST_DELETED, reload );
+ return () => {
+ document.removeEventListener( NN_EVENTS.LOCAL_LIST_SAVED, reload );
+ document.removeEventListener( NN_EVENTS.LOCAL_LIST_DELETED, reload );
+ };
+ }, [] );
+
+ const startFallbackTimer = fallbackUrl => {
+ if ( isBridgeReady() || ! fallbackUrl ) {
+ return;
+ }
+ clearTimeout( fallbackTimerRef.current );
+ fallbackTimerRef.current = setTimeout( () => {
+ if ( ! isBridgeReady() ) {
+ window.location.href = fallbackUrl;
+ }
+ }, NN_FALLBACK_TIMEOUT_MS );
+ };
+
+ const dispatchOpenAdd = () => {
+ document.dispatchEvent( new CustomEvent( NN_EVENTS.OPEN_MODAL, { detail: { mode: 'add' } } ) );
+ startFallbackTimer( newspack_newsletters_wizard.new_subscription_lists_url );
+ };
+ const dispatchOpenEdit = ( list, kind ) => {
+ document.dispatchEvent( new CustomEvent( NN_EVENTS.OPEN_MODAL, { detail: { mode: 'edit', kind, list } } ) );
+ startFallbackTimer( list?.edit_link );
+ };
+ const dispatchConfirmDelete = list => {
+ document.dispatchEvent( new CustomEvent( NN_EVENTS.OPEN_CONFIRM_DELETE, { detail: { list } } ) );
+ startFallbackTimer( list?.edit_link );
+ };
+
if ( ! inFlight && ! lists?.length && ! error ) {
return null;
}
-
if ( inFlight && ! lists?.length && ! error ) {
return (
@@ -323,61 +387,62 @@ export const SubscriptionLists = ( { lockedLists, onUpdate, provider } ) => {
notificationLevel={ error ? 'error' : 'warning' }
hasGreyHeader
actionContent={
- <>
- { newspack_newsletters_wizard.new_subscription_lists_url && (
-
- ) }
-