feat(admin): companion to newspack-newsletters admin UX milestone#4735
Open
thomasguillot wants to merge 21 commits into
Open
feat(admin): companion to newspack-newsletters admin UX milestone#4735thomasguillot wants to merge 21 commits into
thomasguillot wants to merge 21 commits into
Conversation
The `bridgeMounted` flag was only set via the one-shot `newspack-newsletters:bridge-mounted` event listener. If the bridge bundle booted before this module evaluated, the event was missed and the 500ms fallback timer redirected to the legacy editor after every modal-open attempt. Replace the local mutable boolean with a sync read of `window.newspackNewslettersBridgeReady`, which the bridge now sets before dispatching the event. Refs NEWS-2152
…2168)
Stacks the bundled-mode side onto the same wiring as NEWS-2152's
local-list parity. The wizard's SubscriptionLists view dispatches
OPEN_MODAL with kind='esp' for remote rows (replacing the legacy
ExternalLink to the CPT editor) and the active toggle commits
immediately via PATCH /lists/{db_id}.
- Edit on remote rows opens the same modal in ESP mode through the
wizard-bridge (legacy edit_link still serves as the fallback when
the bridge isn't ready).
- Inline TextControl/TextareaControl pair removed for remote rows —
the modal owns title + description.
- Active toggle on every row PATCHes that one row; bulk
"Save Subscription Lists" button removed.
- Description string surfaced under the bold ActionCard title (with
the type label kept on a smaller line below) so publishers see what
customisation is in place without opening the modal.
…263) - integration-icons namespace with 6 brand SVGs (Active Campaign, Constant Contact, Fundraise Up, Mailchimp, Salesforce, Wisepops); fills customisable via var(--integration-icon-color) / --integration-icon-color-accent with brand-colour defaults. - useUnsavedChangesDialog hook: wraps useConfirmDialog with the standard "discard changes" messaging, intercepts outbound link clicks so the dialog fires instead of the silent native one, plus a beforeunload safety net. - CardSettingsGroup gains optional className and iconSize props. - CoreCard accepts iconSize; SCSS reads it via the new --newspack-card-icon-size custom property with fallback to existing defaults so no consumer changes are required. - SectionHeader: container margin now zeroes alongside its inner element when noMargin is set (via :has() so callsites need no change). - admin.css: chrome section header only gets margin-top when it's not the first child of newspack-wizard__content, fixing the doubled spacing on audience /edit/new/all. - Audience content-gates edit adopts useUnsavedChangesDialog so the same standardised confirm flow covers both wizards.
Single-section wizard with sticky header, dirty-state tracking, and
unsaved-changes guard via useUnsavedChangesDialog. Header chrome shows
"Newsletters / Settings" with the Save button disabled until something
is actually dirty.
Layout (top to bottom, two-column rows with section header on the left):
- Email service provider — 2x2 grid of brand-icon cards (Active
Campaign, Mailchimp, Constant Contact, Manual / Other). Selecting a
card sets the provider and reveals its credentials below the grid.
Constant Contact shows its OAuth Authorize prompt inline when needed.
- Newsletter posts — slug, comments, related-posts as ToggleControls.
- Subscription lists — per-row ToggleControl with Local/ESP badge, list
description as help text, dividers between rows, Edit/Delete on the
right (Delete only for local lists). Below the list, a paragraph and
secondary "Add new local list" button explain provider-specific
syncing (label resolved per-ESP via the wizard's settings response).
- Ads tracking — merged in from the previous separate tab;
ToggleControls for click + impressions. The Tracking tab on the
ads-list admin header is removed since the section now lives here.
- Letterhead — separate section at the bottom for the Letterhead API
key + help link.
PHP: wizard /settings response now includes the active provider's
labels (get_labels('local_list_explanation')), guarded with
class_exists for standalone-newspack-plugin installs.
Test selectors updated for the new card-based Add new flow.
6 tasks
Contributor
There was a problem hiding this comment.
Pull request overview
This PR modernizes the Newsletters admin settings experience by consolidating settings, subscription lists, ads tracking, and Letterhead controls into a single Wizard-based page, while adding shared UI scaffolding for future admin UX work.
Changes:
- Rebuilds the Newsletters settings page with fixed-header wizard actions, dirty-state protection, provider cards, list bridge events, inline tracking, and Letterhead settings.
- Adds shared components/hooks support for integration icons, card icon sizing, and standardized unsaved-changes dialogs.
- Updates related admin spacing, content-gates dirty navigation handling, and PHP settings payload/tab behavior.
Reviewed changes
Copilot reviewed 20 out of 22 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/wizards/newsletters/views/settings/index.js |
Rebuilds Newsletter settings UI, save flow, subscription list bridge, dirty tracking, tracking/Letterhead composition. |
src/wizards/newsletters/views/settings/index.test.js |
Adds tests for subscription-list bridge events and immediate active toggles. |
src/wizards/newsletters/views/settings/style.scss |
Adds styling for rebuilt settings layout, list rows, badges, and ESP cards. |
src/wizards/newsletters/views/tracking/index.js |
Converts ads tracking into an embeddable settings section. |
src/wizards/newsletters/index.js |
Replaces legacy tabbed routing with the shared Wizard wrapper. |
includes/wizards/newsletters/class-newsletters-wizard.php |
Adds provider labels to settings response and removes the Tracking admin tab. |
src/wizards/audience/views/content-gates/edit/index.tsx |
Switches content-gate edit navigation guard to the shared unsaved-changes hook. |
packages/components/src/hooks/use-unsaved-changes-dialog.tsx |
Adds reusable unsaved-changes confirmation and beforeunload/link interception. |
packages/components/src/index.js |
Exports the new hook and integration icon namespace. |
packages/components/src/integration-icons/index.js |
Exports integration icon components. |
packages/components/src/integration-icons/active-campaign.js |
Adds Active Campaign SVG icon. |
packages/components/src/integration-icons/constant-contact.js |
Adds Constant Contact SVG icon. |
packages/components/src/integration-icons/fundraise-up.js |
Adds Fundraise Up SVG icon. |
packages/components/src/integration-icons/mailchimp.js |
Adds Mailchimp SVG icon. |
packages/components/src/integration-icons/salesforce.js |
Adds Salesforce SVG icon. |
packages/components/src/integration-icons/wisepops.js |
Adds Wisepops SVG icon. |
packages/components/src/card/core-card.js |
Adds configurable card icon sizing. |
packages/components/src/card/style-core.scss |
Applies icon sizing via a CSS custom property. |
packages/components/src/card-settings-group/index.tsx |
Allows custom class names and icon sizes for settings cards. |
packages/components/src/section-header/style.scss |
Ensures no-margin section headers also reset container margin. |
src/admin/style.scss |
Avoids top margin on the first wizard content section header. |
.gitignore |
Ignores the /build directory. |
Files not reviewed (1)
- packages/components/src/integration-icons/mailchimp.js: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- useUnsavedChangesDialog: track confirmed navigations via a ref so the beforeunload guard skips the native prompt after the user confirms via our custom dialog. - SubscriptionLists: per-row toggles use functional setState and a Set of in-flight db_ids so two quick toggles can't clobber each other if responses resolve out of order; rollback reverts the specific row to its previous active value rather than restoring a whole-list snapshot. - SubscriptionLists: render the locked-state warning even when no list data has loaded yet, so a provider change that sets lockedLists before a successful fetch still explains itself. - Settings (onboarding path): restore the provider filter so unselected ESPs' credential fields don't appear in the setup Services card.
Replace `Provider::get_labels( 'local_list_explanation' )` (where the arg is a context hint, not a key selector — easy to misread) with a direct `Provider::label( 'local_list_explanation' )` call assigned to an explicit `local_list_explanation` key. Same runtime payload, but the intent is now obvious at the call site and the response surfaces only what the client actually consumes.
…s in Router (NEWS-2263)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Companion to the
newspack-newslettersAdmin UX modernisation milestone. The newsletters side is fully merged intoepic/newsletters-modernisation(milestone roll-up: Automattic/newspack-newsletters#2141); this PR carries the matching newspack-plugin work and must merge in lockstep with Automattic/newspack-newsletters#2163 so neither repo lands first and leaves the other broken. Merge #2163 first, then this PR.Originally scoped as wizard-bridge wiring for NEWS-2152 / NEWS-2168 and grew to absorb the broader Newsletters Settings rebuild (NEWS-2263) and the shared scaffolding it needed.
NEWS-2152 / NEWS-2168 — Subscription-list bridge + ESP-edit parity
<SubscriptionLists>per-row Edit / Delete buttons dispatchwizard-bridgeevents instead of opening legacy URLs; reloads on save / delete.kind: 'esp'(no audience picker, title + description only).PATCH /lists/{db_id}; bulk Save button retired./listscalls on manual configs).window.newspackNewslettersEvents(forward-compatible) with a local fallback so the two repos stay in sync.NEWS-2263 — Newsletters Settings page rebuild
Single-section wizard (
fixedHeader, dirty tracking, unsaved-changes guard, save snackbar) with sections, top-to-bottom:ToggleControls.ToggleControlwith Local / ESP badge, list description as help text, divider between rows, Edit / Delete on the right (Delete only for local lists). Below the list, a paragraph + secondary "Add new local list" button explain provider-specific syncing (label resolved per-ESP via the wizard's settings response). Per-row Edit / Delete buttons carryaria-labels with the list name.ToggleControls for click + impressions. Section is gated onconfigured === trueso unconfigured installs don't hit the tracking endpoint. Tracking view file moved intoviews/settings/tracking.jsto colocate with its caller. Tracking tab removed from the ads-list admin header since the section now lives in Settings.Save flow:
setHeaderData; disabled unless dirty.saveSettingssnapshots the payload beforeawaitso the saved-config marker reflects what was actually sent.WizardSnackbar("Settings saved.") via the wizard store'saddNotice.PHP: wizard
/settingsresponse now includes the active provider's labels (Provider::label('local_list_explanation')), guarded withclass_existsfor standalone-newspack-plugin installs.Shared scaffolding (packages/components)
integration-iconsnamespace with 6 brand SVGs (Active Campaign, Constant Contact, Fundraise Up, Mailchimp, Salesforce, Wisepops). AC/CC/MC/FU normalised to a 24-unit viewBox with sizing baked into the path geometry — noiconSizeprop or CSS custom-property indirection.useUnsavedChangesDialoghook — wrapsuseConfirmDialogwith the standard "discard changes" messaging, intercepts same-origin link clicks so the custom dialog fires instead of a silent native one (skipsmailto:,tel:, external origins), plus abeforeunloadsafety net. Warns in dev if more than one instance is active simultaneously (single-consumer constraint of the document-level click capture).CardSettingsGroupgains an optionalclassNameprop.SectionHeader— container margin now zeroes alongside its inner element whennoMarginis set (via:has()so callsites need no change).admin.css— chrome section header only getsmargin-topwhen it's not the first child ofnewspack-wizard__content, fixing the doubled spacing on audience/edit/new/all.useUnsavedChangesDialogso the same standardised confirm flow covers both wizards.Test plan
newspack-newslettersbundle JS to confirm the fallback timer redirects to the legacy URL on Edit; navigate away mid-click to confirm no spurious redirect fires after unmount./listsfetch in the Network tab).mailto:or external link from the same dirty state → no dialog (correct)./edit/new/all: confirm the chrome section header sits flush with the wizard content padding (no doubled top margin) and the discard-changes dialog still fires via the shared hook.Merge order
This PR must land in lockstep with Automattic/newspack-newsletters#2163. Either repo merged first would leave the other temporarily broken in production: the newsletters side ships the new React Settings surface; this side ships the wizard-bridge integration the newsletters side calls into and the rebuilt Settings page that hosts it.
Merge order: #2163 first, then this PR.