Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a1a7570
Implement new site launch and import buttons
epeicher Nov 4, 2025
83e3af9
Fix linting errors
epeicher Nov 4, 2025
d747572
Use connect button when there is a site connected
epeicher Nov 4, 2025
e9cd44b
Merge branch 'trunk' of github.com:Automattic/studio into update/conn…
epeicher Nov 5, 2025
847efa4
Simplify state, update button text and fix tests
epeicher Nov 5, 2025
c643361
Update wording, reuse ConnectButton component
epeicher Nov 5, 2025
59dd4f4
Add a new type and simplify code
epeicher Nov 5, 2025
42d50c4
Fix types
epeicher Nov 5, 2025
2e3fea4
Simplify logic
epeicher Nov 5, 2025
304b9a7
Remove superfluous comment and simplify
epeicher Nov 5, 2025
3ee87ae
Merge branch 'trunk' of github.com:Automattic/studio into update/conn…
epeicher Nov 5, 2025
53b7a23
Use strict equality operator
epeicher Nov 5, 2025
e276491
Use local variable to avoid non-null assertion
epeicher Nov 5, 2025
014d956
Merge branch 'trunk' of github.com:Automattic/studio into update/conn…
epeicher Nov 6, 2025
bb66758
Simplify logic for selecting sites, fix typo
epeicher Nov 6, 2025
38d8ae4
Move SyncModalMode piece of state to Redux
epeicher Nov 6, 2025
c5bca9b
Apply code review suggestion, use Next for Sync dialog button
epeicher Nov 6, 2025
445ab73
Publish on Overview tab for non-connected sites
epeicher Nov 6, 2025
706b4f7
Fix tests
epeicher Nov 6, 2025
5a03ffc
Merge branch 'trunk' of github.com:Automattic/studio into stu-911-stu…
epeicher Nov 7, 2025
e2a165a
Updated colors, wording and added interpolate for RTL as per review
epeicher Nov 7, 2025
a992626
Move button to header actions
epeicher Nov 7, 2025
c800873
Fix issue navigating to the sync tab
epeicher Nov 7, 2025
8c1fe68
Fix issue with refresh not navigating to Sync tab
epeicher Nov 7, 2025
e182f7e
Fix issue with merge
epeicher Nov 7, 2025
606a77e
Code review suggestions
epeicher Nov 7, 2025
9386f72
Add cloudUpload icon to Publish site button
epeicher Nov 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/components/publish-site-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { globe, cloudUpload } from '@wordpress/icons';
import { useI18n } from '@wordpress/react-i18n';
import Button from 'src/components/button';
import { useSyncSites } from 'src/hooks/sync-sites';
import { useContentTabs } from 'src/hooks/use-content-tabs';
import { useAppDispatch } from 'src/stores';
import { connectedSitesActions, useConnectedSitesData } from 'src/stores/sync';
import { Tooltip } from './tooltip';

export const PublishSiteButton = () => {
const { __ } = useI18n();
const dispatch = useAppDispatch();
const { setSelectedTab } = useContentTabs();
const { connectedSites } = useConnectedSitesData();
const { isAnySitePulling, isAnySitePushing } = useSyncSites();
const isAnySiteSyncing = isAnySitePulling || isAnySitePushing;
const handlePublishClick = () => {
setSelectedTab( 'sync' );
dispatch( connectedSitesActions.openModal( 'push' ) );
};

if ( connectedSites.length !== 0 ) return null;

return (
<Tooltip
disabled={ ! isAnySiteSyncing }
text={ __(
'Another site is syncing. Please wait for the sync to finish before you publish your site.'
) }
placement="left"
>
<Button
variant="primary"
disabled={ isAnySiteSyncing }
aria-label={ __( 'Publish site' ) }
onClick={ handlePublishClick }
icon={ cloudUpload }
>
{ __( 'Publish site' ) }
</Button>
</Tooltip>
);
};
24 changes: 21 additions & 3 deletions src/components/site-content-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,34 @@ export function SiteContentTabs() {
// Remount: Avoid focus loss on user tab changes (no remount),
// but remount on programmatic changes and site switches so initial tab/content state resets.
const [ keyCounter, setKeyCounter ] = useState( 0 );
const [ programmaticTab, setProgrammaticTab ] = useState( selectedTab );
const lastChangeWasUser = useRef( false );
const isFirstRender = useRef( true );
const prevSelectedTab = useRef( selectedTab );

useEffect( () => {
if ( isFirstRender.current ) {
isFirstRender.current = false;
prevSelectedTab.current = selectedTab;
return;
}

// If tab didn't actually change, skip
if ( prevSelectedTab.current === selectedTab ) {
return;
}

// Check if this was a user action by seeing if the flag was set BEFORE selectedTab changed
if ( lastChangeWasUser.current ) {
lastChangeWasUser.current = false;
prevSelectedTab.current = selectedTab;
return;
}

// Programmatic change - update both counter and tab to force remount
setKeyCounter( ( k ) => k + 1 );
setProgrammaticTab( selectedTab );
prevSelectedTab.current = selectedTab;
}, [ selectedTab ] );

if ( ! loadingSites && ! localSites.length ) {
Expand Down Expand Up @@ -71,12 +86,15 @@ export function SiteContentTabs() {
tabs={ tabs }
orientation="horizontal"
onSelect={ ( tabName ) => {
// Mark this as a user-initiated change so we don't remount
lastChangeWasUser.current = true;
// Mark this as a user-initiated change BEFORE calling setSelectedTab
// so the useEffect can detect it was user-initiated
if ( tabName !== selectedTab ) {
lastChangeWasUser.current = true;
}
setSelectedTab( tabName as TabName );
} }
initialTabName={ selectedTab }
key={ `${ selectedSite.id }-${ keyCounter }` }
key={ `${ selectedSite.id }-${ keyCounter }-${ programmaticTab }` }
>
{ ( { name } ) => (
<div
Expand Down
43 changes: 24 additions & 19 deletions src/components/site-management-actions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { __ } from '@wordpress/i18n';
import { useI18n } from '@wordpress/react-i18n';
import { ActionButton } from 'src/components/action-button';
import { PublishSiteButton } from 'src/components/publish-site-button';
import { Tooltip } from 'src/components/tooltip';
import { useSyncSites } from 'src/hooks/sync-sites';
import { useImportExport } from 'src/hooks/use-import-export';
Expand All @@ -21,6 +22,7 @@ export const SiteManagementActions = ( {
const { __ } = useI18n();
const { isSiteImporting } = useImportExport();
const { isSiteIdPulling } = useSyncSites();

if ( ! selectedSite ) {
return null;
}
Expand All @@ -35,24 +37,27 @@ export const SiteManagementActions = ( {
}

return (
<Tooltip
disabled={ ! disabled }
text={ __( "A site can't be stopped or started during import." ) }
placement="left"
>
<ActionButton
isRunning={ selectedSite.running }
isLoading={ loading }
onClick={ () => {
if ( selectedSite.running ) {
void onStop( selectedSite.id );
} else {
void onStart( selectedSite.id );
}
} }
disabled={ disabled }
buttonLabelOnDisabled={ buttonLabelOnDisabled }
/>
</Tooltip>
<div className="flex gap-2">
<PublishSiteButton />
<Tooltip
disabled={ ! disabled }
text={ __( "A site can't be stopped or started during import." ) }
placement="left"
>
<ActionButton
isRunning={ selectedSite.running }
isLoading={ loading }
onClick={ () => {
if ( selectedSite.running ) {
void onStop( selectedSite.id );
} else {
void onStart( selectedSite.id );
}
} }
disabled={ disabled }
buttonLabelOnDisabled={ buttonLabelOnDisabled }
/>
</Tooltip>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { fireEvent, render, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import { ContentTabOverview } from 'src/components/content-tab-overview';
import { ContentTabsProvider } from 'src/hooks/use-content-tabs';
import { useThemeDetails } from 'src/hooks/use-theme-details';
import { isMac } from 'src/lib/app-globals';
import { getIpcApi } from 'src/lib/get-ipc-api';
Expand All @@ -26,12 +27,23 @@ const selectedSite: StartedSiteDetails = {
const mockGetIpcApi = getIpcApi as jest.Mock;
jest.mock( 'src/lib/get-ipc-api' );
jest.mock( 'src/hooks/use-theme-details' );
jest.mock( 'src/stores/sync', () => ( {
...jest.requireActual( 'src/stores/sync' ),
useConnectedSitesData: jest.fn().mockReturnValue( {
connectedSites: [],
localSiteId: 'site-id',
} ),
} ) );

// Replace the store's reducer with our test reducer
store.replaceReducer( testReducer );

function renderWithProvider( component: React.ReactElement ) {
return render( <Provider store={ store }>{ component }</Provider> );
return render(
<Provider store={ store }>
<ContentTabsProvider>{ component }</ContentTabsProvider>
</Provider>
);
}

describe( 'ShortcutsSection', () => {
Expand Down
5 changes: 4 additions & 1 deletion src/components/tests/content-tab-overview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { render, screen } from '@testing-library/react';
import { Provider } from 'react-redux';
import { ContentTabOverview } from 'src/components/content-tab-overview';
import { ContentTabsProvider } from 'src/hooks/use-content-tabs';
import { useSiteDetails } from 'src/hooks/use-site-details';
import { useThemeDetails } from 'src/hooks/use-theme-details';
import { store } from 'src/stores';
Expand Down Expand Up @@ -77,7 +78,9 @@ describe( 'ContentTabOverview', () => {
} );
render(
<Provider store={ store }>
<ContentTabOverview selectedSite={ selectedSite } />
<ContentTabsProvider>
<ContentTabOverview selectedSite={ selectedSite } />
</ContentTabsProvider>
</Provider>
);
};
Expand Down
Loading