diff --git a/apps/studio/src/ipc-handlers.ts b/apps/studio/src/ipc-handlers.ts index 0b2d00abee..d16afe5be8 100644 --- a/apps/studio/src/ipc-handlers.ts +++ b/apps/studio/src/ipc-handlers.ts @@ -196,6 +196,7 @@ export { downloadSyncBackup, exportSiteForPush, fetchSyncableWpcomSites, + fetchSyncableWpcomSitesPage, getConnectedWpcomSites, pauseSyncUpload, pullSiteFromLive, diff --git a/apps/studio/src/modules/sync/lib/ipc-handlers.ts b/apps/studio/src/modules/sync/lib/ipc-handlers.ts index f33ff10efa..23b074ef4a 100644 --- a/apps/studio/src/modules/sync/lib/ipc-handlers.ts +++ b/apps/studio/src/modules/sync/lib/ipc-handlers.ts @@ -12,7 +12,11 @@ import { } from '@studio/common/lib/connected-sites'; import { isErrnoException } from '@studio/common/lib/is-errno-exception'; import { getCurrentUserId } from '@studio/common/lib/shared-config'; -import { fetchSyncableSites } from '@studio/common/lib/sync/sync-api'; +import { + fetchSyncableSites, + fetchSyncableSitesPage, + type SyncableSitesPage, +} from '@studio/common/lib/sync/sync-api'; import wpcomFactory from '@studio/common/lib/wpcom-factory'; import wpcomXhrRequest from '@studio/common/lib/wpcom-xhr-request-factory'; import { SyncSite } from '@studio/common/types/sync'; @@ -524,7 +528,24 @@ export async function fetchSyncableWpcomSites( _event: IpcMainInvokeEvent ): Pro if ( ! token?.accessToken ) { throw new Error( 'Authentication required to fetch WordPress.com sites.' ); } - return fetchSyncableSites( token.accessToken ); + // Pass the already-connected remote IDs so the transform can mark those + // sites 'already-connected' instead of offering them as syncable again. + const connectedSites = await getAllConnectedWpcomSitesForCurrentUser(); + const connectedSiteIds = connectedSites.map( ( site ) => site.id ); + return fetchSyncableSites( token.accessToken, { connectedSiteIds } ); +} + +export async function fetchSyncableWpcomSitesPage( + _event: IpcMainInvokeEvent, + options: { page?: number; perPage?: number; search?: string } = {} +): Promise< SyncableSitesPage > { + const token = await getAuthenticationToken(); + if ( ! token?.accessToken ) { + throw new Error( 'Authentication required to fetch WordPress.com sites.' ); + } + const connectedSites = await getAllConnectedWpcomSitesForCurrentUser(); + const connectedSiteIds = connectedSites.map( ( site ) => site.id ); + return fetchSyncableSitesPage( token.accessToken, { ...options, connectedSiteIds } ); } export async function getConnectedWpcomSites( diff --git a/apps/studio/src/preload.ts b/apps/studio/src/preload.ts index b92f50046d..ddc428fe70 100644 --- a/apps/studio/src/preload.ts +++ b/apps/studio/src/preload.ts @@ -135,6 +135,8 @@ const api: IpcApi = { getConnectedWpcomSites: ( localSiteId ) => ipcRendererInvoke( 'getConnectedWpcomSites', localSiteId ), fetchSyncableWpcomSites: () => ipcRendererInvoke( 'fetchSyncableWpcomSites' ), + fetchSyncableWpcomSitesPage: ( options ) => + ipcRendererInvoke( 'fetchSyncableWpcomSitesPage', options ), pullSiteFromLive: ( siteFolder, remoteSiteId ) => ipcRendererInvoke( 'pullSiteFromLive', siteFolder, remoteSiteId ), addSyncOperation: ( id, status ) => ipcRendererSend( 'addSyncOperation', id, status ), diff --git a/apps/ui/package.json b/apps/ui/package.json index 02ca4db048..8719231a4d 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -17,6 +17,7 @@ "@tanstack/react-query": "^5.75.5", "@tanstack/react-query-persist-client": "^5.96.2", "@tanstack/react-router": "^1.120.14", + "@wordpress/a11y": "^4.47.0", "@wordpress/api-fetch": "^7.47.0", "@wordpress/components": "^34.0.0", "@wordpress/core-data": "^7.47.0", diff --git a/apps/ui/src/components/busy-overlay/index.tsx b/apps/ui/src/components/busy-overlay/index.tsx new file mode 100644 index 0000000000..cacaddc34e --- /dev/null +++ b/apps/ui/src/components/busy-overlay/index.tsx @@ -0,0 +1,15 @@ +import styles from './style.module.css'; + +/** + * Transparent full-window shield that blocks pointer interaction while a + * long-running action (site creation, connect-and-pull) finishes. Pair it + * with disabled/loading states on the triggering button — it deliberately + * covers everything, including the onboarding close button, so a stray + * click can't interrupt the work mid-flight. + */ +export function BusyOverlay( { active }: { active: boolean } ) { + if ( ! active ) { + return null; + } + return