11import { check , Icon } from '@wordpress/icons' ;
22import { useI18n } from '@wordpress/react-i18n' ;
3- import { PropsWithChildren , useEffect } from 'react' ;
3+ import { PropsWithChildren , useEffect , useState } from 'react' ;
44import { ArrowIcon } from 'src/components/arrow-icon' ;
55import Button from 'src/components/button' ;
66import offlineIcon from 'src/components/offline-icon' ;
77import { Tooltip } from 'src/components/tooltip' ;
8+ import { useSyncSites } from 'src/hooks/sync-sites' ;
89import { useAuth } from 'src/hooks/use-auth' ;
910import { useOffline } from 'src/hooks/use-offline' ;
1011import { getIpcApi } from 'src/lib/get-ipc-api' ;
1112import { ConnectButton } from 'src/modules/sync/components/connect-button' ;
1213import { SyncConnectedSites } from 'src/modules/sync/components/sync-connected-sites' ;
14+ import { SyncDialog } from 'src/modules/sync/components/sync-dialog' ;
1315import { SyncSitesModalSelector } from 'src/modules/sync/components/sync-sites-modal-selector' ;
1416import { SyncTabImage } from 'src/modules/sync/components/sync-tab-image' ;
17+ import {
18+ convertTreeToPullOptions ,
19+ convertTreeToPushOptions ,
20+ } from 'src/modules/sync/lib/convert-tree-to-sync-options' ;
1521import { useAppDispatch , useRootSelector } from 'src/stores' ;
1622import {
1723 useConnectedSitesData ,
@@ -21,6 +27,7 @@ import {
2127 connectedSitesActions ,
2228} from 'src/stores/sync' ;
2329import type { SyncSite } from 'src/hooks/use-fetch-wpcom-sites/types' ;
30+ import type { SyncModalMode } from 'src/modules/sync/types' ;
2431
2532function SiteSyncDescription ( { children } : PropsWithChildren ) {
2633 const { __ } = useI18n ( ) ;
@@ -34,13 +41,13 @@ function SiteSyncDescription( { children }: PropsWithChildren ) {
3441 </ div >
3542 < div className = "max-w-[40ch] text-a8c-gray-70 a8c-body" >
3643 { __ (
37- 'Connect your existing WordPress.com or Pressable sites with Jetpack activated , or create a new one. Then share your work with the world.'
44+ 'Launch your existing WordPress.com or Jetpack-activated Pressable sites, or import an existing one. Then, share your work with the world.'
3845 ) }
3946 </ div >
4047 < div className = "mt-6" >
4148 { [
4249 __ ( 'Push and pull changes from your live site.' ) ,
43- __ ( 'Connect multiple environments .' ) ,
50+ __ ( 'Supports staging and production sites .' ) ,
4451 __ ( 'Sync database and file changes.' ) ,
4552 ] . map ( ( text ) => (
4653 < div key = { text } className = "text-a8c-gray-70 a8c-body flex items-center" >
@@ -120,9 +127,14 @@ export function ContentTabSync( { selectedSite }: { selectedSite: SiteDetails }
120127 const { __ } = useI18n ( ) ;
121128 const dispatch = useAppDispatch ( ) ;
122129 const isModalOpen = useRootSelector ( connectedSitesSelectors . selectIsModalOpen ) ;
130+ const reduxModalMode = useRootSelector ( connectedSitesSelectors . selectModalMode ) ;
123131 const { connectedSites } = useConnectedSitesData ( ) ;
124132 const { syncSites, isFetching, refetchSites } = useSyncSitesData ( ) ;
125133 const { connectSite, disconnectSite } = useConnectedSitesOperations ( ) ;
134+ const { pushSite, pullSite, isAnySitePulling, isAnySitePushing } = useSyncSites ( ) ;
135+ const isAnySiteSyncing = isAnySitePulling || isAnySitePushing ;
136+
137+ const [ selectedRemoteSite , setSelectedRemoteSite ] = useState < SyncSite | null > ( null ) ;
126138
127139 const { isAuthenticated } = useAuth ( ) ;
128140
@@ -147,6 +159,41 @@ export function ContentTabSync( { selectedSite }: { selectedSite: SiteDetails }
147159 }
148160 } ;
149161
162+ const handleLaunchSite = ( ) => {
163+ dispatch ( connectedSitesActions . openModal ( 'push' ) ) ;
164+ } ;
165+
166+ const handleImportSite = ( ) => {
167+ dispatch ( connectedSitesActions . openModal ( 'pull' ) ) ;
168+ } ;
169+
170+ const handleSiteSelection = async ( siteId : number , mode : SyncModalMode | null ) => {
171+ const disconnectSiteId =
172+ typeof isModalOpen === 'object' ? isModalOpen . disconnectSiteId : undefined ;
173+
174+ if ( disconnectSiteId ) {
175+ await disconnectSite ( disconnectSiteId ) ;
176+ }
177+
178+ const selectedSiteFromList = syncSites . find ( ( site ) => site . id === siteId ) ;
179+ if ( ! selectedSiteFromList ) {
180+ getIpcApi ( ) . showErrorMessageBox ( {
181+ title : __ ( 'Failed to select site' ) ,
182+ message : __ ( 'Please try again.' ) ,
183+ } ) ;
184+ return ;
185+ }
186+
187+ if ( mode === 'push' || mode === 'pull' ) {
188+ dispatch ( connectedSitesActions . setModalMode ( mode ) ) ;
189+ setSelectedRemoteSite ( selectedSiteFromList ) ;
190+ } else {
191+ await handleConnect ( selectedSiteFromList ) ;
192+ dispatch ( connectedSitesActions . setModalMode ( null ) ) ;
193+ dispatch ( connectedSitesActions . closeModal ( ) ) ;
194+ }
195+ } ;
196+
150197 return (
151198 < div className = "flex flex-col h-full overflow-y-auto" >
152199 { connectedSites . length > 0 ? (
@@ -159,54 +206,79 @@ export function ContentTabSync( { selectedSite }: { selectedSite: SiteDetails }
159206 < div className = "sticky bottom-0 bg-white/[0.8] backdrop-blur-sm w-full px-8 py-6 mt-auto" >
160207 < ConnectButton
161208 variant = "primary"
162- connectSite = { ( ) => dispatch ( connectedSitesActions . openModal ( ) ) }
163- disableConnectButtonStyle = { true }
209+ connectSite = { ( ) => dispatch ( connectedSitesActions . openModal ( 'connect' ) ) }
164210 >
165211 { __ ( 'Connect another site' ) }
166212 </ ConnectButton >
167213 </ div >
168214 </ div >
169215 ) : (
170216 < SiteSyncDescription >
171- < div className = "mt-8" >
217+ < div className = "mt-8 flex flex-wrap gap-4 " >
172218 < ConnectButton
173219 variant = "primary"
174- connectSite = { ( ) => dispatch ( connectedSitesActions . openModal ( ) ) }
175- disableConnectButtonStyle = { true }
220+ connectSite = { handleLaunchSite }
221+ disabled = { isAnySiteSyncing }
222+ tooltipText = {
223+ isAnySiteSyncing
224+ ? __ (
225+ 'Another site is syncing. Please wait for the sync to finish before you publish your site.'
226+ )
227+ : __ ( 'Publishing your site requires an internet connection.' )
228+ }
229+ >
230+ { __ ( 'Publish site' ) }
231+ </ ConnectButton >
232+ < ConnectButton
233+ variant = "secondary"
234+ connectSite = { handleImportSite }
235+ className = { isAnySiteSyncing ? '' : '!text-a8c-blue-50 !shadow-a8c-blue-50' }
236+ disabled = { isAnySiteSyncing }
237+ tooltipText = { __ ( 'Importing a remote site requires an internet connection.' ) }
176238 >
177- { __ ( 'Connect site' ) }
239+ { __ ( 'Pull site' ) }
178240 </ ConnectButton >
179241 </ div >
180242 </ SiteSyncDescription >
181243 ) }
182244
183245 { isModalOpen && (
184246 < SyncSitesModalSelector
247+ mode = { reduxModalMode || 'connect' }
185248 isLoading = { isFetching }
186- onRequestClose = { ( ) => dispatch ( connectedSitesActions . closeModal ( ) ) }
249+ onRequestClose = { ( ) => {
250+ dispatch ( connectedSitesActions . closeModal ( ) ) ;
251+ } }
187252 syncSites = { syncSites }
188253 onInitialRender = { refetchSites }
189- onConnect = { async ( siteId ) => {
190- const disconnectSiteId =
191- typeof isModalOpen === 'object' ? isModalOpen . disconnectSiteId : undefined ;
192-
193- if ( disconnectSiteId ) {
194- await disconnectSite ( disconnectSiteId ) ;
195- }
196-
197- const newConnectedSite = syncSites . find ( ( site ) => site . id === siteId ) ;
198- if ( ! newConnectedSite ) {
199- getIpcApi ( ) . showErrorMessageBox ( {
200- title : __ ( 'Failed to connect to site' ) ,
201- message : __ ( 'Please try again.' ) ,
202- } ) ;
203- return ;
204- }
205- void handleConnect ( newConnectedSite ) ;
254+ onConnect = { async ( siteId : number ) => {
255+ await handleSiteSelection ( siteId , reduxModalMode ) ;
206256 } }
207257 selectedSite = { selectedSite }
208258 />
209259 ) }
260+
261+ { reduxModalMode && reduxModalMode !== 'connect' && selectedRemoteSite && (
262+ < SyncDialog
263+ type = { reduxModalMode }
264+ localSite = { selectedSite }
265+ remoteSite = { selectedRemoteSite }
266+ onPush = { async ( tree ) => {
267+ await handleConnect ( selectedRemoteSite ) ;
268+ const pushOptions = convertTreeToPushOptions ( tree ) ;
269+ void pushSite ( selectedRemoteSite , selectedSite , pushOptions ) ;
270+ } }
271+ onPull = { async ( tree ) => {
272+ await handleConnect ( selectedRemoteSite ) ;
273+ const pullOptions = convertTreeToPullOptions ( tree ) ;
274+ void pullSite ( selectedRemoteSite , selectedSite , pullOptions ) ;
275+ } }
276+ onRequestClose = { ( ) => {
277+ setSelectedRemoteSite ( null ) ;
278+ dispatch ( connectedSitesActions . setModalMode ( null ) ) ;
279+ } }
280+ />
281+ ) }
210282 </ div >
211283 ) ;
212284}
0 commit comments