diff --git a/apps/studio/src/hooks/sync-sites/use-listen-deep-link-connection.ts b/apps/studio/src/hooks/sync-sites/use-listen-deep-link-connection.ts
index f329986bfe..8f36cfedb1 100644
--- a/apps/studio/src/hooks/sync-sites/use-listen-deep-link-connection.ts
+++ b/apps/studio/src/hooks/sync-sites/use-listen-deep-link-connection.ts
@@ -3,12 +3,7 @@ import { useContentTabs } from 'src/hooks/use-content-tabs';
import { useIpcListener } from 'src/hooks/use-ipc-listener';
import { useSiteDetails } from 'src/hooks/use-site-details';
import { useAppDispatch } from 'src/stores';
-import {
- connectedSitesActions,
- useConnectSiteMutation,
- useGetConnectedSitesForLocalSiteQuery,
-} from 'src/stores/sync/connected-sites';
-import { useGetWpComSitesQuery } from 'src/stores/sync/wpcom-sites';
+import { connectedSitesActions, useConnectSiteMutation } from 'src/stores/sync/connected-sites';
export function useListenDeepLinkConnection() {
const dispatch = useAppDispatch();
@@ -16,37 +11,22 @@ export function useListenDeepLinkConnection() {
const { selectedSite, setSelectedSiteId } = useSiteDetails();
const { setSelectedTab, selectedTab } = useContentTabs();
const { user } = useAuth();
- const { data: connectedSites = [] } = useGetConnectedSitesForLocalSiteQuery( {
- localSiteId: selectedSite?.id,
- userId: user?.id,
- } );
- const connectedSiteIds = connectedSites.map( ( { id } ) => id );
- const { refetch: refetchWpComSites } = useGetWpComSitesQuery( {
- connectedSiteIds,
- userId: user?.id,
- } );
useIpcListener(
'sync-connect-site',
async ( _event, { remoteSiteId, studioSiteId, autoOpenPush } ) => {
- // Fetch latest sites from network before checking
- const result = await refetchWpComSites();
- const latestSites = result.data ?? [];
- const newConnectedSite = latestSites.find( ( site ) => site.id === remoteSiteId );
- if ( newConnectedSite ) {
- if ( selectedSite?.id && selectedSite.id !== studioSiteId ) {
- // Select studio site that started the sync
- setSelectedSiteId( studioSiteId );
- }
- await connectSite( { site: newConnectedSite, localSiteId: studioSiteId } );
- if ( selectedTab !== 'sync' ) {
- // Switch to sync tab
- setSelectedTab( 'sync' );
- }
- // Only auto-open push dialog if explicitly requested (e.g., from "Publish site" button)
- if ( autoOpenPush ) {
- dispatch( connectedSitesActions.setSelectedRemoteSiteId( remoteSiteId ) );
- }
+ if ( selectedSite?.id && selectedSite.id !== studioSiteId ) {
+ // Select studio site that started the sync
+ setSelectedSiteId( studioSiteId );
+ }
+ await connectSite( { remoteSiteId, localSiteId: studioSiteId, userId: user?.id } );
+ if ( selectedTab !== 'sync' ) {
+ // Switch to sync tab
+ setSelectedTab( 'sync' );
+ }
+ // Only auto-open push dialog if explicitly requested (e.g., from "Publish site" button)
+ if ( autoOpenPush ) {
+ dispatch( connectedSitesActions.setSelectedRemoteSiteId( remoteSiteId ) );
}
}
);
diff --git a/apps/studio/src/hooks/tests/use-add-site.test.tsx b/apps/studio/src/hooks/tests/use-add-site.test.tsx
index ffa99d2306..98efb7fbed 100644
--- a/apps/studio/src/hooks/tests/use-add-site.test.tsx
+++ b/apps/studio/src/hooks/tests/use-add-site.test.tsx
@@ -9,6 +9,7 @@ import { useContentTabs } from 'src/hooks/use-content-tabs';
import { useSiteDetails } from 'src/hooks/use-site-details';
import { store } from 'src/stores';
import { setProviderConstants } from 'src/stores/provider-constants-slice';
+import { useConnectSiteMutation } from 'src/stores/sync/connected-sites';
import type { SyncSitesContextType } from 'src/hooks/sync-sites/sync-sites-context';
import type { SyncSite } from 'src/modules/sync/types';
@@ -16,6 +17,13 @@ vi.mock( 'src/hooks/use-site-details' );
vi.mock( 'src/hooks/use-feature-flags' );
vi.mock( 'src/hooks/sync-sites' );
vi.mock( 'src/hooks/use-content-tabs' );
+vi.mock( 'src/stores/sync/connected-sites', async ( importOriginal ) => {
+ const original = await importOriginal< typeof import('src/stores/sync/connected-sites') >();
+ return {
+ ...original,
+ useConnectSiteMutation: vi.fn(),
+ };
+} );
vi.mock( 'src/hooks/use-import-export', () => ( {
useImportExport: () => ( {
importFile: vi.fn(),
@@ -24,6 +32,7 @@ vi.mock( 'src/hooks/use-import-export', () => ( {
} ),
} ) );
+const mockConnectSite = vi.fn().mockReturnValue( { unwrap: () => Promise.resolve( [] ) } );
const mockConnectWpcomSites = vi.fn().mockResolvedValue( undefined );
const mockShowOpenFolderDialog = vi.fn();
const mockGenerateProposedSitePath = vi.fn().mockResolvedValue( {
@@ -62,6 +71,11 @@ describe( 'useAddSite', () => {
beforeEach( () => {
vi.clearAllMocks();
+ vi.mocked( useConnectSiteMutation ).mockReturnValue( [
+ mockConnectSite,
+ { isLoading: false, reset: vi.fn() },
+ ] as unknown as ReturnType< typeof useConnectSiteMutation > );
+
// Prepopulate store with provider constants
store.dispatch(
setProviderConstants( {
@@ -267,12 +281,10 @@ describe( 'useAddSite', () => {
await result.current.handleCreateSite( formValues );
} );
- expect( mockConnectWpcomSites ).toHaveBeenCalledWith( [
- {
- sites: [ remoteSite ],
- localSiteId: createdSite.id,
- },
- ] );
+ expect( mockConnectSite ).toHaveBeenCalledWith( {
+ remoteSiteId: remoteSite.id,
+ localSiteId: createdSite.id,
+ } );
expect( mockPullSite ).toHaveBeenCalledWith( remoteSite, createdSite, {
optionsToSync: [ 'all' ],
} );
diff --git a/apps/studio/src/hooks/use-add-site.ts b/apps/studio/src/hooks/use-add-site.ts
index 8ba7590196..9f66619fe5 100644
--- a/apps/studio/src/hooks/use-add-site.ts
+++ b/apps/studio/src/hooks/use-add-site.ts
@@ -277,7 +277,7 @@ export function useAddSite() {
body: __( 'Your new site was imported' ),
} );
} else if ( selectedRemoteSite ) {
- await connectSite( { site: selectedRemoteSite, localSiteId: newSite.id } );
+ await connectSite( { remoteSiteId: selectedRemoteSite.id, localSiteId: newSite.id } );
const pullOptions: SyncOption[] = [ 'all' ];
pullSite( selectedRemoteSite, newSite, {
optionsToSync: pullOptions,
diff --git a/apps/studio/src/modules/sync/index.tsx b/apps/studio/src/modules/sync/index.tsx
index f04cc2caa3..9f6ec0f687 100644
--- a/apps/studio/src/modules/sync/index.tsx
+++ b/apps/studio/src/modules/sync/index.tsx
@@ -29,34 +29,34 @@ import {
import { useGetWpComSitesQuery } from 'src/stores/sync/wpcom-sites';
import type { SyncSite } from 'src/modules/sync/types';
-function SiteSyncDescription( { children }: PropsWithChildren ) {
+function SiteSyncDescription({ children }: PropsWithChildren) {
const { __ } = useI18n();
return (
- { __( 'Sync with WordPress.com or Pressable' ) }
+ {__('Sync with WordPress.com or Pressable')}
- { __(
+ {__(
'Launch your existing WordPress.com or Jetpack-activated Pressable sites, or import an existing one. Then, share your work with the world.'
- ) }
+ )}
- { [
- __( 'Push and pull changes from your live site.' ),
- __( 'Supports staging and production sites.' ),
- __( 'Sync database and file changes.' ),
- ].map( ( text ) => (
-
-
- { text }
+ {[
+ __('Push and pull changes from your live site.'),
+ __('Supports staging and production sites.'),
+ __('Sync database and file changes.'),
+ ].map((text) => (
+
+
+ {text}
- ) ) }
+ ))}
- { children }
+ {children}
@@ -69,49 +69,49 @@ function NoAuthSyncTab() {
const isOffline = useOffline();
const { __ } = useI18n();
const { authenticate } = useAuth();
- const offlineMessage = __( "You're currently offline." );
+ const offlineMessage = __("You're currently offline.");
return (
-
+
{
- if ( isOffline ) {
+ onClick={() => {
+ if (isOffline) {
return;
}
authenticate();
- } }
+ }}
>
- { __( 'Log in to WordPress.com' ) }
+ {__('Log in to WordPress.com')}
- { __( 'New to WordPress.com?' ) }{ ' ' }
+ {__('New to WordPress.com?')}{' '}
{
- if ( isOffline ) {
+ onClick={() => {
+ if (isOffline) {
return;
}
- getIpcApi().authenticate( true );
- } }
+ getIpcApi().authenticate(true);
+ }}
>
- { __( 'Create a free account' ) }
+ {__('Create a free account')}
@@ -121,94 +121,94 @@ function NoAuthSyncTab() {
);
}
-export function ContentTabSync( { selectedSite }: { selectedSite: SiteDetails } ) {
+export function ContentTabSync({ selectedSite }: { selectedSite: SiteDetails }) {
const { __ } = useI18n();
const dispatch = useAppDispatch();
- const isModalOpen = useRootSelector( connectedSitesSelectors.selectIsModalOpen );
- const reduxModalMode = useRootSelector( connectedSitesSelectors.selectModalMode );
- const selectedRemoteSiteId = useRootSelector(
- connectedSitesSelectors.selectSelectedRemoteSiteId
- );
+ const isModalOpen = useRootSelector(connectedSitesSelectors.selectIsModalOpen);
+ const reduxModalMode = useRootSelector(connectedSitesSelectors.selectModalMode);
+ const selectedRemoteSiteId = useRootSelector(connectedSitesSelectors.selectSelectedRemoteSiteId);
const { isAuthenticated, user } = useAuth();
- const { data: connectedSites = [] } = useGetConnectedSitesForLocalSiteQuery( {
+ const { data: connectedSites = [] } = useGetConnectedSitesForLocalSiteQuery({
localSiteId: selectedSite.id,
userId: user?.id,
- } );
- const [ connectSite ] = useConnectSiteMutation();
- const [ disconnectSite ] = useDisconnectSiteMutation();
+ });
+ const [connectSite] = useConnectSiteMutation();
+ const [disconnectSite] = useDisconnectSiteMutation();
const { pushSite, pullSite } = useSyncSites();
- const connectedSiteIds = connectedSites.map( ( { id } ) => id );
- const { data: syncSites = [] } = useGetWpComSitesQuery( {
+ const connectedSiteIds = connectedSites.map(({ id }) => id);
+ const { data: syncSites = [] } = useGetWpComSitesQuery({
connectedSiteIds,
userId: user?.id,
- } );
+ });
- const [ selectedRemoteSite, setSelectedRemoteSite ] = useState< SyncSite | null >( null );
+ const [selectedRemoteSite, setSelectedRemoteSite] = useState(null);
// Auto-select remote site when set via Redux (e.g., from deep link connection)
- useEffect( () => {
- if ( selectedRemoteSiteId ) {
- const siteToSelect = syncSites.find( ( site ) => site.id === selectedRemoteSiteId );
- if ( siteToSelect ) {
- setSelectedRemoteSite( siteToSelect );
- dispatch( connectedSitesActions.openModal( 'push' ) );
- dispatch( connectedSitesActions.clearSelectedRemoteSiteId() );
+ useEffect(() => {
+ if (selectedRemoteSiteId) {
+ const siteToSelect = syncSites.find((site) => site.id === selectedRemoteSiteId);
+ if (siteToSelect) {
+ setSelectedRemoteSite(siteToSelect);
+ dispatch(connectedSitesActions.openModal('push'));
+ dispatch(connectedSitesActions.clearSelectedRemoteSiteId());
}
}
- }, [ selectedRemoteSiteId, syncSites, dispatch ] );
+ }, [selectedRemoteSiteId, syncSites, dispatch]);
- if ( ! isAuthenticated ) {
+ if (!isAuthenticated) {
return ;
}
- const handleConnect = async ( newConnectedSite: SyncSite ) => {
+ const handleConnect = async (remoteSiteId: number) => {
try {
- await connectSite( { site: newConnectedSite, localSiteId: selectedSite.id } );
- } catch ( error ) {
- getIpcApi().showErrorMessageBox( {
- title: __( 'Failed to connect to site' ),
- message: __( 'Please try again.' ),
- } );
+ await connectSite({
+ remoteSiteId,
+ localSiteId: selectedSite.id,
+ userId: user?.id,
+ });
+ } catch (error) {
+ getIpcApi().showErrorMessageBox({
+ title: __('Failed to connect to site'),
+ message: __('Please try again.'),
+ });
}
};
- const handleSiteSelection = async ( siteId: number ) => {
- const selectedSiteFromList = syncSites.find( ( site ) => site.id === siteId );
- if ( ! selectedSiteFromList ) {
- getIpcApi().showErrorMessageBox( {
- title: __( 'Failed to select site' ),
- message: __( 'Please try again.' ),
- } );
+ const handleSiteSelection = async (siteId: number) => {
+ const selectedSiteFromList = syncSites.find((site) => site.id === siteId);
+ if (!selectedSiteFromList) {
+ getIpcApi().showErrorMessageBox({
+ title: __('Failed to select site'),
+ message: __('Please try again.'),
+ });
return;
}
- if ( reduxModalMode === 'push' || reduxModalMode === 'pull' ) {
- dispatch( connectedSitesActions.openModal( reduxModalMode ) );
- setSelectedRemoteSite( selectedSiteFromList );
+ if (reduxModalMode === 'push' || reduxModalMode === 'pull') {
+ dispatch(connectedSitesActions.openModal(reduxModalMode));
+ setSelectedRemoteSite(selectedSiteFromList);
} else {
- await handleConnect( selectedSiteFromList );
- dispatch( connectedSitesActions.closeModal() );
+ await handleConnect(siteId);
+ dispatch(connectedSitesActions.closeModal());
}
};
return (
- { connectedSites.length > 0 ? (
+ {connectedSites.length > 0 ? (
- disconnectSite( { siteId: id, localSiteId: selectedSite.id } )
- }
+ connectedSites={connectedSites}
+ selectedSite={selectedSite}
+ disconnectSite={(id) => disconnectSite({ siteId: id, localSiteId: selectedSite.id })}
/>
dispatch( connectedSitesActions.openModal( 'connect' ) ) }
+ connectSite={() => dispatch(connectedSitesActions.openModal('connect'))}
>
- { __( 'Connect another site' ) }
+ {__('Connect another site')}
@@ -217,48 +217,48 @@ export function ContentTabSync( { selectedSite }: { selectedSite: SiteDetails }
dispatch( connectedSitesActions.openModal( 'connect' ) ) }
+ connectSite={() => dispatch(connectedSitesActions.openModal('connect'))}
>
- { __( 'Connect site' ) }
+ {__('Connect site')}
- ) }
+ )}
- { isModalOpen && ! selectedRemoteSite && (
+ {isModalOpen && !selectedRemoteSite && (
{
- dispatch( connectedSitesActions.closeModal() );
- } }
- onConnect={ async ( siteId: number ) => {
- await handleSiteSelection( siteId );
- } }
- selectedSite={ selectedSite }
+ mode={reduxModalMode || 'connect'}
+ onRequestClose={() => {
+ dispatch(connectedSitesActions.closeModal());
+ }}
+ onConnect={async (siteId: number) => {
+ await handleSiteSelection(siteId);
+ }}
+ selectedSite={selectedSite}
/>
- ) }
+ )}
- { reduxModalMode && reduxModalMode !== 'connect' && selectedRemoteSite && (
+ {reduxModalMode && reduxModalMode !== 'connect' && selectedRemoteSite && (
{
- await handleConnect( selectedRemoteSite );
- const pushOptions = convertTreeToPushOptions( tree );
- void pushSite( selectedRemoteSite, selectedSite, pushOptions );
- } }
- onPull={ async ( tree ) => {
- await handleConnect( selectedRemoteSite );
- const pullOptions = convertTreeToPullOptions( tree );
- void pullSite( selectedRemoteSite, selectedSite, pullOptions );
- } }
- onRequestClose={ () => {
- setSelectedRemoteSite( null );
- dispatch( connectedSitesActions.closeModal() );
- } }
+ type={reduxModalMode}
+ localSite={selectedSite}
+ remoteSite={selectedRemoteSite}
+ onPush={async (tree) => {
+ await handleConnect(selectedRemoteSite.id);
+ const pushOptions = convertTreeToPushOptions(tree);
+ void pushSite(selectedRemoteSite, selectedSite, pushOptions);
+ }}
+ onPull={async (tree) => {
+ await handleConnect(selectedRemoteSite.id);
+ const pullOptions = convertTreeToPullOptions(tree);
+ void pullSite(selectedRemoteSite, selectedSite, pullOptions);
+ }}
+ onRequestClose={() => {
+ setSelectedRemoteSite(null);
+ dispatch(connectedSitesActions.closeModal());
+ }}
/>
- ) }
+ )}
);
}
diff --git a/apps/studio/src/stores/sync/connected-sites.ts b/apps/studio/src/stores/sync/connected-sites.ts
index 6b2796eba1..bf3e517082 100644
--- a/apps/studio/src/stores/sync/connected-sites.ts
+++ b/apps/studio/src/stores/sync/connected-sites.ts
@@ -2,6 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { getIpcApi } from 'src/lib/get-ipc-api';
import { RootState } from 'src/stores';
+import { wpcomSitesApi } from 'src/stores/sync/wpcom-sites';
import type { SyncSite, SyncModalMode } from 'src/modules/sync/types';
type ConnectedSitesState = {
@@ -74,11 +75,35 @@ export const connectedSitesApi = createApi( {
],
} ),
- connectSite: builder.mutation< SyncSite[], { site: SyncSite; localSiteId: string } >( {
- queryFn: async ( { site, localSiteId } ) => {
+ connectSite: builder.mutation<
+ SyncSite[],
+ { remoteSiteId: number; localSiteId: string; userId?: number }
+ >( {
+ queryFn: async ( { remoteSiteId, localSiteId, userId }, api ) => {
+ const connectedSites = await getIpcApi().getConnectedWpcomSites( localSiteId );
+ const { data: remoteSites = [] } = await api.dispatch(
+ wpcomSitesApi.endpoints.getWpComSites.initiate(
+ {
+ connectedSiteIds: connectedSites.map( ( site ) => site.id ),
+ userId,
+ },
+ { forceRefetch: true }
+ )
+ );
+ const siteToConnect = remoteSites.find( ( site ) => site.id === remoteSiteId );
+
+ if ( ! siteToConnect ) {
+ return {
+ error: {
+ status: 'CUSTOM_ERROR',
+ error: 'Site not found in WordPress.com sites',
+ },
+ };
+ }
+
await getIpcApi().connectWpcomSites( [
{
- sites: [ site ],
+ sites: [ siteToConnect ],
localSiteId,
},
] );