From fb41967f01bd7265130703219c7ba5224bc43dae Mon Sep 17 00:00:00 2001 From: William Viana Date: Thu, 12 Mar 2026 10:44:08 +0100 Subject: [PATCH 1/6] My Jetpack: Move notices and JITMs below tabs Notices (bad-install, connection errors) and JITMs were rendering between the page header and the tab bar. Move them into the tab content area via a `beforeContent` prop on MyJetpackTabPanel, so they appear below the tabs and separator line. Co-Authored-By: Claude Opus 4.6 --- .../components/my-jetpack-screen/index.jsx | 45 ++++++++++--------- .../components/my-jetpack-tab-panel/index.tsx | 21 +++++++-- .../my-jetpack-tab-panel/tab-content.tsx | 2 - 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx b/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx index 761045ceaa6d..2af68f9896fd 100644 --- a/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx +++ b/projects/packages/my-jetpack/_inc/components/my-jetpack-screen/index.jsx @@ -189,30 +189,35 @@ export default function MyJetpackScreen() {

{ __( 'My Jetpack', 'jetpack-my-jetpack' ) }

- - { ! isNewUser && ( - - -
- - - ) } - { noticeMessage && ( - - - - - - ) } { isSectionVisible && userIsAdmin && } { isRedirectingFromOnboarding && } - + + + { ! isNewUser && ( + + +
+ + + ) } + { noticeMessage && ( + + + + + + ) } + + } + /> ); } diff --git a/projects/packages/my-jetpack/_inc/components/my-jetpack-tab-panel/index.tsx b/projects/packages/my-jetpack/_inc/components/my-jetpack-tab-panel/index.tsx index a52cec2fddc4..edef32cf2a98 100644 --- a/projects/packages/my-jetpack/_inc/components/my-jetpack-tab-panel/index.tsx +++ b/projects/packages/my-jetpack/_inc/components/my-jetpack-tab-panel/index.tsx @@ -5,17 +5,21 @@ import { useNavigate, useParams } from 'react-router'; import useAnalytics from '../../hooks/use-analytics'; import useIsJetpackUserNew from '../../hooks/use-is-jetpack-user-new'; import { MY_JETPACK_SECTION_OVERVIEW } from './constants'; +import { FullWidthSeparator } from './full-width-separator'; import styles from './styles.module.scss'; import { TabContent } from './tab-content'; import { MyJetpackSection } from './types'; import { getMyJetpackSections, isValidMyJetpackSection } from './utils'; +import type { ReactNode } from 'react'; /** * My Jetpack Tab panel component. * + * @param {object} root0 - Component props. + * @param {ReactNode} root0.beforeContent - Content to render between the tab separator and tab content. * @return The rendered component. */ -export function MyJetpackTabPanel() { +export function MyJetpackTabPanel( { beforeContent }: { beforeContent?: ReactNode } ) { const params = useParams(); const navigate = useNavigate(); const { recordEvent } = useAnalytics(); @@ -55,9 +59,18 @@ export function MyJetpackTabPanel() { [ navigate, params.section, recordEvent, isNewUser ] ); - const tabRenderer = useCallback( ( tab: { name: string } ) => { - return ; - }, [] ); + const tabRenderer = useCallback( + ( tab: { name: string } ) => { + return ( + <> + + { beforeContent } + + + ); + }, + [ beforeContent ] + ); // Handle external navigation (URL changes not from tab clicks) useEffect( () => { diff --git a/projects/packages/my-jetpack/_inc/components/my-jetpack-tab-panel/tab-content.tsx b/projects/packages/my-jetpack/_inc/components/my-jetpack-tab-panel/tab-content.tsx index 560ba63551b9..26e03d77f069 100644 --- a/projects/packages/my-jetpack/_inc/components/my-jetpack-tab-panel/tab-content.tsx +++ b/projects/packages/my-jetpack/_inc/components/my-jetpack-tab-panel/tab-content.tsx @@ -1,5 +1,4 @@ import clsx from 'clsx'; -import { FullWidthSeparator } from './full-width-separator'; import { HelpContent } from './help/content'; import { OverviewContent } from './overview/content'; import { ProductsContent } from './products/content'; @@ -33,7 +32,6 @@ export function TabContent( { name }: TabContentProps ) { return (
-
From 0a14102ad0fdb67bf17aeb24abf34a1b204e4827 Mon Sep 17 00:00:00 2001 From: William Viana Date: Thu, 12 Mar 2026 11:08:11 +0100 Subject: [PATCH 2/6] Social, Search: Move notices and JITMs below page header Move the JITM slot and connection error notices so they render below the page header instead of inside it. Social: Move #jp-admin-notices and ConnectionError from the Header component into the main admin page, after AdminSectionHero. Search: Move the JITM container after MockedSearchInterface. Add a useEffect to relocate stray JITMs placed by JITM JS before React mounts, and a CSS rule to hide them as a fallback. Co-Authored-By: Claude Opus 4.6 --- .../changelog/fix-admin-notices-inside-header | 4 ++ .../components/admin-page/header/index.js | 49 +++++++------------ .../_inc/components/admin-page/index.tsx | 17 ++++++- .../changelog/fix-admin-notices-inside-header | 4 ++ .../changelog/fix-admin-notices-inside-header | 4 ++ .../components/pages/dashboard-page.jsx | 24 +++++++-- .../components/pages/dashboard-page.scss | 4 ++ 7 files changed, 69 insertions(+), 37 deletions(-) create mode 100644 projects/packages/my-jetpack/changelog/fix-admin-notices-inside-header create mode 100644 projects/packages/publicize/changelog/fix-admin-notices-inside-header create mode 100644 projects/packages/search/changelog/fix-admin-notices-inside-header diff --git a/projects/packages/my-jetpack/changelog/fix-admin-notices-inside-header b/projects/packages/my-jetpack/changelog/fix-admin-notices-inside-header new file mode 100644 index 000000000000..bea8b1a04d18 --- /dev/null +++ b/projects/packages/my-jetpack/changelog/fix-admin-notices-inside-header @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Move admin notices and JITMs below the tab bar instead of rendering inside the page header. diff --git a/projects/packages/publicize/_inc/components/admin-page/header/index.js b/projects/packages/publicize/_inc/components/admin-page/header/index.js index 694aee4b888c..d2e233e4ece3 100644 --- a/projects/packages/publicize/_inc/components/admin-page/header/index.js +++ b/projects/packages/publicize/_inc/components/admin-page/header/index.js @@ -1,5 +1,4 @@ import { Button, Col, Container, H3 } from '@automattic/jetpack-components'; -import { ConnectionError, useConnectionErrorNotice } from '@automattic/jetpack-connection'; import { getAdminUrl } from '@automattic/jetpack-script-data'; import { useDispatch, useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -15,41 +14,27 @@ const Header = () => { }; } ); - const { hasConnectionError } = useConnectionErrorNotice(); - const { openConnectionsModal } = useDispatch( socialStore ); return ( - <> - - { hasConnectionError && ( - - - - ) } - -
- - - - -

{ __( 'Write once, post everywhere', 'jetpack-publicize-pkg' ) }

-
- { isModuleEnabled && ! hasConnections && ( - - ) } - -
- -
- + ) } + +
+ +
); }; diff --git a/projects/packages/publicize/_inc/components/admin-page/index.tsx b/projects/packages/publicize/_inc/components/admin-page/index.tsx index e5d94e1c2220..55636e19b6db 100644 --- a/projects/packages/publicize/_inc/components/admin-page/index.tsx +++ b/projects/packages/publicize/_inc/components/admin-page/index.tsx @@ -6,7 +6,11 @@ import { Col, GlobalNotices, } from '@automattic/jetpack-components'; -import { useConnection } from '@automattic/jetpack-connection'; +import { + ConnectionError, + useConnection, + useConnectionErrorNotice, +} from '@automattic/jetpack-connection'; import { getMyJetpackUrl, isJetpackSelfHostedSite, @@ -38,6 +42,7 @@ export const SocialAdminPage = () => { const isJetpackSite = isJetpackSelfHostedSite(); const { isUserConnected, isRegistered } = useConnection(); + const { hasConnectionError } = useConnectionErrorNotice(); const showConnectionCard = ! isSimple && ( ! isRegistered || ! isUserConnected ); const [ pricingPageDismissed, setPricingPageDismissed ] = useState( false ); @@ -112,6 +117,16 @@ export const SocialAdminPage = () => {
+ + { hasConnectionError && ( + + + + ) } + +
+ + { canManageOptions && ( diff --git a/projects/packages/publicize/changelog/fix-admin-notices-inside-header b/projects/packages/publicize/changelog/fix-admin-notices-inside-header new file mode 100644 index 000000000000..9825c5c69f3c --- /dev/null +++ b/projects/packages/publicize/changelog/fix-admin-notices-inside-header @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Move admin notices and JITMs below the page header instead of rendering inside it. diff --git a/projects/packages/search/changelog/fix-admin-notices-inside-header b/projects/packages/search/changelog/fix-admin-notices-inside-header new file mode 100644 index 000000000000..9825c5c69f3c --- /dev/null +++ b/projects/packages/search/changelog/fix-admin-notices-inside-header @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Move admin notices and JITMs below the page header instead of rendering inside it. diff --git a/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx b/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx index 62cbba5ecf6d..4ab8e2b6ae6f 100644 --- a/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx +++ b/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx @@ -9,6 +9,7 @@ import { import { useConnectionErrorNotice, ConnectionError } from '@automattic/jetpack-connection'; import { shouldUseInternalLinks } from '@automattic/jetpack-shared-extension-utils'; import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import NoticesList from 'components/global-notices'; import Loading from 'components/loading'; @@ -33,6 +34,21 @@ export default function DashboardPage( { isLoading = false } ) { useSelect( select => select( STORE_ID ).getSearchModuleStatus(), [] ); useSelect( select => select( STORE_ID ).getSearchStats(), [] ); + // The JITM JS may run before React mounts, placing .jitm-card in #wpbody-content + // instead of #jp-admin-notices. Move any stray JITMs into the correct container. + useEffect( () => { + if ( isPageLoading ) { + return; + } + const target = document.getElementById( 'jp-admin-notices' ); + if ( ! target ) { + return; + } + document.querySelectorAll( '#wpbody-content > .jitm-card' ).forEach( card => { + target.appendChild( card ); + } ); + }, [ isPageLoading ] ); + const domain = useSelect( select => select( STORE_ID ).getCalypsoSlug() ); const blogID = useSelect( select => select( STORE_ID ).getBlogId() ); const siteAdminUrl = useSelect( select => select( STORE_ID ).getSiteAdminUrl() ); @@ -126,6 +142,10 @@ export default function DashboardPage( { isLoading = false } ) { className="uses-new-admin-ui" showFooter={ false } > + { hasConnectionError && ( @@ -136,10 +156,6 @@ export default function DashboardPage( { isLoading = false } ) {
- { isNewPricing && supportsInstantSearch && ( .notice { display: none; } + + > .jitm-card { + display: none; + } } .jetpack-search-jitm-card { From 071eb3626535bb120fbfc5421af7c061a1023c1e Mon Sep 17 00:00:00 2001 From: William Viana Date: Thu, 12 Mar 2026 16:20:58 +0100 Subject: [PATCH 3/6] Search: Fix useEffect referencing isPageLoading before declaration Move the JITM-relocation useEffect below the isPageLoading declaration to avoid a ReferenceError from the temporal dead zone. Variables declared with const are not hoisted, so the useEffect callback and deps array must come after the const. Co-Authored-By: Claude Opus 4.6 --- .../components/pages/dashboard-page.jsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx b/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx index 4ab8e2b6ae6f..90a2cf0fcb6d 100644 --- a/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx +++ b/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx @@ -34,21 +34,6 @@ export default function DashboardPage( { isLoading = false } ) { useSelect( select => select( STORE_ID ).getSearchModuleStatus(), [] ); useSelect( select => select( STORE_ID ).getSearchStats(), [] ); - // The JITM JS may run before React mounts, placing .jitm-card in #wpbody-content - // instead of #jp-admin-notices. Move any stray JITMs into the correct container. - useEffect( () => { - if ( isPageLoading ) { - return; - } - const target = document.getElementById( 'jp-admin-notices' ); - if ( ! target ) { - return; - } - document.querySelectorAll( '#wpbody-content > .jitm-card' ).forEach( card => { - target.appendChild( card ); - } ); - }, [ isPageLoading ] ); - const domain = useSelect( select => select( STORE_ID ).getCalypsoSlug() ); const blogID = useSelect( select => select( STORE_ID ).getBlogId() ); const siteAdminUrl = useSelect( select => select( STORE_ID ).getSiteAdminUrl() ); @@ -77,6 +62,21 @@ export default function DashboardPage( { isLoading = false } ) { [ isLoading ] ); + // The JITM JS may run before React mounts, placing .jitm-card in #wpbody-content + // instead of #jp-admin-notices. Move any stray JITMs into the correct container. + useEffect( () => { + if ( isPageLoading ) { + return; + } + const target = document.getElementById( 'jp-admin-notices' ); + if ( ! target ) { + return; + } + document.querySelectorAll( '#wpbody-content > .jitm-card' ).forEach( card => { + target.appendChild( card ); + } ); + }, [ isPageLoading ] ); + // Introduce the gate for new pricing with URL parameter `new_pricing_202208=1` const isNewPricing = useSelect( select => select( STORE_ID ).isNewPricing202208(), [] ); From 158db14df83a5228cff73b3e9f41d5c21953cdba Mon Sep 17 00:00:00 2001 From: William Viana Date: Thu, 12 Mar 2026 16:40:46 +0100 Subject: [PATCH 4/6] Social: Fix dropped CSS module class for connection error When ConnectionError was moved from the Header component to the main admin page, the CSS module reference styles['connection-error-col'] was replaced with a bare string class name that had no matching CSS rule, silently dropping the margin-top: 25px styling. Move the .connection-error-col rule to the admin page's SCSS module and restore the CSS module reference. Co-Authored-By: Claude Opus 4.6 --- .../_inc/components/admin-page/header/styles.module.scss | 4 ---- .../packages/publicize/_inc/components/admin-page/index.tsx | 4 ++-- .../publicize/_inc/components/admin-page/styles.module.scss | 4 ++++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/projects/packages/publicize/_inc/components/admin-page/header/styles.module.scss b/projects/packages/publicize/_inc/components/admin-page/header/styles.module.scss index d0d97cee02d4..9c8638a69b66 100644 --- a/projects/packages/publicize/_inc/components/admin-page/header/styles.module.scss +++ b/projects/packages/publicize/_inc/components/admin-page/header/styles.module.scss @@ -57,10 +57,6 @@ } } -.connection-error-col { - margin-top: 25px; -} - .bar-wrapper { margin-bottom: var(--spacing-base); diff --git a/projects/packages/publicize/_inc/components/admin-page/index.tsx b/projects/packages/publicize/_inc/components/admin-page/index.tsx index 55636e19b6db..70906df7e48c 100644 --- a/projects/packages/publicize/_inc/components/admin-page/index.tsx +++ b/projects/packages/publicize/_inc/components/admin-page/index.tsx @@ -29,7 +29,7 @@ import ConnectionScreen from './connection-screen'; import Header from './header'; import InfoSection from './info-section'; import PricingPage from './pricing-page'; -import './styles.module.scss'; +import styles from './styles.module.scss'; import SupportSection from './support-section'; import SocialImageGeneratorToggle from './toggles/social-image-generator-toggle'; import SocialModuleToggle from './toggles/social-module-toggle'; @@ -119,7 +119,7 @@ export const SocialAdminPage = () => { { hasConnectionError && ( - + ) } diff --git a/projects/packages/publicize/_inc/components/admin-page/styles.module.scss b/projects/packages/publicize/_inc/components/admin-page/styles.module.scss index 44901e1385cc..654492123840 100644 --- a/projects/packages/publicize/_inc/components/admin-page/styles.module.scss +++ b/projects/packages/publicize/_inc/components/admin-page/styles.module.scss @@ -23,3 +23,7 @@ .notice { grid-column: 1 / -1; } + +.connection-error-col { + margin-top: 25px; +} From c3d9df9ac22b273b27db94e5517c3e67a6196b05 Mon Sep 17 00:00:00 2001 From: William Viana Date: Thu, 12 Mar 2026 18:47:31 +0100 Subject: [PATCH 5/6] Social: Add JITM slot to pricing page branch The JITM slot and ConnectionError only existed in the non-pricing branch. When the pricing interstitial was shown, there was no #jp-admin-notices element, so JITMs fell back to the PHP placeholder above the header. Add the JITM container inside the AdminSectionHero wrapping the pricing page so it renders properly within the content flow on both paths. Co-Authored-By: Claude Opus 4.6 --- .../publicize/_inc/components/admin-page/index.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/projects/packages/publicize/_inc/components/admin-page/index.tsx b/projects/packages/publicize/_inc/components/admin-page/index.tsx index 70906df7e48c..fcd6bfbe4041 100644 --- a/projects/packages/publicize/_inc/components/admin-page/index.tsx +++ b/projects/packages/publicize/_inc/components/admin-page/index.tsx @@ -106,6 +106,16 @@ export const SocialAdminPage = () => { { isJetpackSite && ! hasSocialPaidFeatures() && showPricingPage && ! pricingPageDismissed ? ( + + { hasConnectionError && ( + + + + ) } + +
+ + From 483f8437e45f57140c25632f19d300506452de49 Mon Sep 17 00:00:00 2001 From: William Viana Date: Tue, 17 Mar 2026 00:22:56 +0100 Subject: [PATCH 6/6] Search: Render JITM container unconditionally to eliminate race condition workarounds Move #jp-admin-notices outside the isPageLoading conditional so it's always in the DOM. JITM JS now always finds it via Path A (prepend into existing element) instead of falling back to Path B (replace placeholder in #wpbody-content). This removes the need for: - useEffect that relocated stray JITM cards after React loaded - CSS fallback hiding #wpbody-content > .jitm-card Also conditionally renders the Container/Col around ConnectionError only when there's actually a connection error, removing the always-present empty Col that previously held the notices div. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/pages/dashboard-page.jsx | 29 ++++--------------- .../components/pages/dashboard-page.scss | 4 --- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx b/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx index 90a2cf0fcb6d..467941c9cb59 100644 --- a/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx +++ b/projects/packages/search/src/dashboard/components/pages/dashboard-page.jsx @@ -9,7 +9,6 @@ import { import { useConnectionErrorNotice, ConnectionError } from '@automattic/jetpack-connection'; import { shouldUseInternalLinks } from '@automattic/jetpack-shared-extension-utils'; import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import NoticesList from 'components/global-notices'; import Loading from 'components/loading'; @@ -62,21 +61,6 @@ export default function DashboardPage( { isLoading = false } ) { [ isLoading ] ); - // The JITM JS may run before React mounts, placing .jitm-card in #wpbody-content - // instead of #jp-admin-notices. Move any stray JITMs into the correct container. - useEffect( () => { - if ( isPageLoading ) { - return; - } - const target = document.getElementById( 'jp-admin-notices' ); - if ( ! target ) { - return; - } - document.querySelectorAll( '#wpbody-content > .jitm-card' ).forEach( card => { - target.appendChild( card ); - } ); - }, [ isPageLoading ] ); - // Introduce the gate for new pricing with URL parameter `new_pricing_202208=1` const isNewPricing = useSelect( select => select( STORE_ID ).isNewPricing202208(), [] ); @@ -123,6 +107,8 @@ export default function DashboardPage( { isLoading = false } ) { return ( <> + { /* Always in the DOM so JITM JS finds it immediately (Path A). */ } +
{ isPageLoading && } { ! isPageLoading && (
@@ -146,16 +132,13 @@ export default function DashboardPage( { isLoading = false } ) { supportsInstantSearch={ supportsInstantSearch } supportsOnlyClassicSearch={ supportsOnlyClassicSearch } /> - - { hasConnectionError && ( + { hasConnectionError && ( + - ) } - -
- - + + ) } { isNewPricing && supportsInstantSearch && ( .notice { display: none; } - - > .jitm-card { - display: none; - } } .jetpack-search-jitm-card {