Skip to content

feat(admin): companion to newspack-newsletters admin UX milestone#4735

Open
thomasguillot wants to merge 21 commits into
trunkfrom
news-admin-ux-modernisation
Open

feat(admin): companion to newspack-newsletters admin UX milestone#4735
thomasguillot wants to merge 21 commits into
trunkfrom
news-admin-ux-modernisation

Conversation

@thomasguillot
Copy link
Copy Markdown
Contributor

@thomasguillot thomasguillot commented May 18, 2026

Companion to the newspack-newsletters Admin UX modernisation milestone. The newsletters side is fully merged into epic/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 dispatch wizard-bridge events instead of opening legacy URLs; reloads on save / delete.
  • Edit on remote rows goes through the bridge with kind: 'esp' (no audience picker, title + description only).
  • Active toggle commits per-row via PATCH /lists/{db_id}; bulk Save button retired.
  • Section is not rendered for the Manual / Other provider (Newsletters API rejects /lists calls on manual configs).
  • 500 ms fallback timer routes to the legacy CPT editor when the bridge bundle is missing; cleared on unmount so a scheduled redirect can't fire after the component is gone.
  • Wizard-bridge event names lazily read from 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:

  • Email service provider — 2x2 brand-icon card grid (Active Campaign, Mailchimp, Constant Contact, Manual / Other). Selecting a card sets the provider and reveals its credentials below the grid. OAuth Authorize prompt inlines for Constant Contact. Cards and credential inputs are disabled while a save is in flight to prevent a stale-config race.
  • Newsletter posts — slug, comments, related-posts as ToggleControls.
  • Subscription lists — per-row ToggleControl with 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 carry aria-labels with the list name.
  • Ads tracking — merged in from the previous separate tab; ToggleControls for click + impressions. Section is gated on configured === true so unconfigured installs don't hit the tracking endpoint. Tracking view file moved into views/settings/tracking.js to colocate with its caller. Tracking tab removed from the ads-list admin header since the section now lives in Settings.
  • Letterhead — separate section at the bottom for the Letterhead API key + help link; input disabled during save.

Save flow:

  • Top-right Save button registered via setHeaderData; disabled unless dirty.
  • saveSettings snapshots the payload before await so the saved-config marker reflects what was actually sent.
  • On success, fires a WizardSnackbar ("Settings saved.") via the wizard store's addNotice.
  • Unsaved-changes guard (shared hook — see scaffolding) blocks navigation when dirty.

PHP: wizard /settings response now includes the active provider's labels (Provider::label('local_list_explanation')), guarded with class_exists for standalone-newspack-plugin installs.

Shared scaffolding (packages/components)

  • integration-icons namespace 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 — no iconSize prop or CSS custom-property indirection.
  • useUnsavedChangesDialog hook — wraps useConfirmDialog with the standard "discard changes" messaging, intercepts same-origin link clicks so the custom dialog fires instead of a silent native one (skips mailto:, tel:, external origins), plus a beforeunload safety net. Warns in dev if more than one instance is active simultaneously (single-consumer constraint of the document-level click capture).
  • CardSettingsGroup gains an optional className prop.
  • 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.

Test plan

  • Smoke-test Add / Edit / Delete on a Mailchimp + Constant Contact dev site for both local and remote rows.
  • Verify the bottom Save Subscription Lists button is gone and the active toggle on a remote row persists across reloads.
  • Disable newspack-newsletters bundle 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.
  • Newsletters Settings: pick each ESP card, verify its credential fields appear; verify the four ESP icons render at consistent visual weight against their colored backgrounds.
  • Select Manual / Other and confirm the Subscription Lists section is hidden (no locked-state warning, no /lists fetch in the Network tab).
  • Click Save and confirm ESP cards / credential inputs / Letterhead input go disabled while the request is in flight; confirm a "Settings saved." snackbar appears on success.
  • Edit a setting, navigate away with a same-origin link → custom discard-changes dialog fires. Click a mailto: or external link from the same dirty state → no dialog (correct).
  • Toggle Ads Tracking on a configured site; uninstall / disable newsletters and confirm the Tracking section is not rendered (no console error from the tracking endpoint).
  • Audience access control /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.

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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/wizards/newsletters/views/settings/index.js Outdated
Comment thread src/wizards/newsletters/views/settings/index.js Outdated
Comment thread src/wizards/newsletters/views/settings/index.js Outdated
- 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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • packages/components/src/integration-icons/mailchimp.js: Language not supported

Comment thread includes/wizards/newsletters/class-newsletters-wizard.php Outdated
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.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • packages/components/src/integration-icons/mailchimp.js: Language not supported

Comment thread src/wizards/newsletters/views/settings/index.js
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • packages/components/src/integration-icons/mailchimp.js: Language not supported

Comment thread src/wizards/newsletters/views/settings/index.js Outdated
Comment thread src/wizards/newsletters/views/settings/index.js Outdated
@thomasguillot thomasguillot requested a review from Copilot May 19, 2026 14:08
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 24 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • packages/components/src/integration-icons/mailchimp.js: Language not supported

Comment thread packages/components/src/hooks/use-unsaved-changes-dialog.tsx
Comment thread src/wizards/newsletters/views/settings/index.js
@thomasguillot thomasguillot requested a review from Copilot May 19, 2026 14:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 24 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • packages/components/src/integration-icons/mailchimp.js: Language not supported

Comment thread packages/components/src/hooks/use-unsaved-changes-dialog.tsx
Comment thread src/wizards/newsletters/views/settings/index.test.js Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 24 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • packages/components/src/integration-icons/mailchimp.js: Language not supported

@thomasguillot thomasguillot changed the title feat(admin): admin UX modernisation — settings rebuild + shared scaffolding (NEWS-2152, NEWS-2168, NEWS-2263) feat(admin): companion to newspack-newsletters admin UX milestone May 21, 2026
@thomasguillot thomasguillot marked this pull request as ready for review May 21, 2026 13:37
@thomasguillot thomasguillot requested a review from a team as a code owner May 21, 2026 13:37
@thomasguillot thomasguillot added the [Status] Needs Review The issue or pull request needs to be reviewed label May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Newspack Newsletters [Status] Needs Review The issue or pull request needs to be reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants