Skip to content
Open
Show file tree
Hide file tree
Changes from 19 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
37 changes: 37 additions & 0 deletions src/components/content-tab-overview.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as Sentry from '@sentry/electron/renderer';
import { Icon } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import {
archive,
code,
desktop,
pencil,
info,
layout,
navigation,
page,
Expand All @@ -16,15 +18,19 @@ import {
import { useI18n } from '@wordpress/react-i18n';
import { useState } from 'react';
import { ArrowIcon } from 'src/components/arrow-icon';
import Button from 'src/components/button';
import { ButtonsSection, ButtonsSectionProps } from 'src/components/buttons-section';
import { useContentTabs } from 'src/hooks/use-content-tabs';
import { useSiteDetails } from 'src/hooks/use-site-details';
import { useThemeDetails } from 'src/hooks/use-theme-details';
import { isWindows } from 'src/lib/app-globals';
import { cx } from 'src/lib/cx';
import { getIpcApi } from 'src/lib/get-ipc-api';
import { supportedEditorConfig } from 'src/modules/user-settings/lib/editor';
import { getTerminalName } from 'src/modules/user-settings/lib/terminal';
import { useAppDispatch } from 'src/stores';
import { useGetUserEditorQuery, useGetUserTerminalQuery } from 'src/stores/installed-apps-api';
import { connectedSitesActions, useConnectedSitesData } from 'src/stores/sync';

interface ContentTabOverviewProps {
selectedSite: SiteDetails;
Expand Down Expand Up @@ -181,6 +187,36 @@ function ShortcutsSection( { selectedSite }: Pick< ContentTabOverviewProps, 'sel
return <ButtonsSection buttonsArray={ buttonsArray } title={ __( 'Open in…' ) } />;
}

function PublishBanner( {
selectedSite: _selectedSite,
}: Pick< ContentTabOverviewProps, 'selectedSite' > ) {
const { __ } = useI18n();
const dispatch = useAppDispatch();
const { setSelectedTab } = useContentTabs();
const { connectedSites } = useConnectedSitesData();

const handlePublishClick = () => {
// Navigate to Sync tab and open modal with 'push' mode
setSelectedTab( 'sync' );
dispatch( connectedSitesActions.openModal( 'push' ) );
};

// Only show banner if site has no connected sites
if ( connectedSites.length > 0 ) {
return null;
}

return (
<div className="w-full flex items-center gap-3 px-4 py-3 rounded-sm bg-a8c-gray-0 border border-a8c-gray-5">
<Icon icon={ info } size={ 20 } className="text-a8c-blue-50 flex-shrink-0" />
<p className="flex-1 text-black">{ __( 'Your site is ready for publishing' ) }</p>
<Button variant="primary" onClick={ handlePublishClick } disabled={ false }>
{ __( 'Publish site' ) }
</Button>
</div>
);
}

export function ContentTabOverview( { selectedSite }: ContentTabOverviewProps ) {
const [ isThumbnailError, setIsThumbnailError ] = useState( false );
const { __ } = useI18n();
Expand Down Expand Up @@ -260,6 +296,7 @@ export function ContentTabOverview( { selectedSite }: ContentTabOverviewProps )
</div>
</div>
<div className="flex flex-1 flex-col justify-start items-start gap-8">
<PublishBanner selectedSite={ selectedSite } />
<CustomizeSection
selectedSite={ selectedSite }
themeDetails={ themeDetails }
Expand Down
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
5 changes: 4 additions & 1 deletion src/modules/sync/components/connect-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface ConnectButtonProps {
disableConnectButtonStyle?: boolean;
className?: string;
children?: React.ReactNode;
tooltipText?: string;
}

export const ConnectButton = ( {
Expand All @@ -19,12 +20,14 @@ export const ConnectButton = ( {
disableConnectButtonStyle,
className,
children,
tooltipText,
}: ConnectButtonProps ) => {
const isOffline = useOffline();
const tooltipContent = tooltipText ?? __( 'Connecting a site requires an internet connection.' );
return (
<Tooltip
disabled={ ! isOffline }
text={ __( 'Connecting a site requires an internet connection.' ) }
text={ tooltipContent }
icon={ offlineIcon }
placement="top-start"
>
Expand Down
52 changes: 46 additions & 6 deletions src/modules/sync/components/sync-sites-modal-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { EnvironmentBadge } from 'src/modules/sync/components/environment-badge'
import { getSiteEnvironment } from 'src/modules/sync/lib/environment-utils';
import { useI18nLocale } from 'src/stores';
import type { SyncSite } from 'src/hooks/use-fetch-wpcom-sites/types';
import type { SyncModalMode } from 'src/modules/sync/types';

const SearchControl = process.env.NODE_ENV === 'test' ? () => null : SearchControlWp;

Expand All @@ -32,13 +33,15 @@ export function SyncSitesModalSelector( {
syncSites,
onInitialRender,
selectedSite,
mode = 'connect',
}: {
isLoading?: boolean;
onRequestClose: () => void;
syncSites: SyncSite[];
onConnect: ( siteId: number ) => void;
onInitialRender?: () => void;
selectedSite: SiteDetails;
mode?: SyncModalMode;
} ) {
const { __ } = useI18n();
const [ selectedSiteId, setSelectedSiteId ] = useState< number | null >( null );
Expand All @@ -53,6 +56,18 @@ export function SyncSitesModalSelector( {
} );
const isEmpty = filteredSites.length === 0;

const getModalTitle = () => {
switch ( mode ) {
case 'push':
return __( 'Publish your site' );
case 'pull':
return __( 'Select a site to import' );
case 'connect':
default:
return __( 'Connect your site' );
}
};

useEffect( () => {
if ( onInitialRender ) {
onInitialRender();
Expand All @@ -63,7 +78,7 @@ export function SyncSitesModalSelector( {
<Modal
className="w-3/5 min-w-[550px] h-full max-h-[84vh] [&>div]:!p-0"
onRequestClose={ onRequestClose }
title={ __( 'Connect your site' ) }
title={ getModalTitle() }
>
<div className="relative" data-testid="sync-sites-modal-selector">
<SearchSites searchQuery={ searchQuery } setSearchQuery={ setSearchQuery } />
Expand Down Expand Up @@ -100,11 +115,12 @@ export function SyncSitesModalSelector( {
} }
disabled={ ! selectedSiteId }
selectedSite={ selectedSite }
mode={ mode }
/>

{ isOffline && (
<div className="absolute inset-0 bg-white/80 z-10 flex items-center justify-center">
<SyncSitesOfflineView />
<SyncSitesOfflineView mode={ mode } />
</div>
) }
</div>
Expand Down Expand Up @@ -335,14 +351,27 @@ function Footer( {
onConnect,
disabled,
selectedSite,
mode = 'connect',
}: {
onRequestClose: () => void;
onConnect: () => void;
disabled: boolean;
selectedSite: SiteDetails;
mode?: SyncModalMode;
} ) {
const { __ } = useI18n();

const getButtonText = () => {
switch ( mode ) {
case 'push':
case 'pull':
return __( 'Next' );
case 'connect':
default:
return __( 'Connect' );
}
};

useEffect( () => {
if ( ! disabled ) {
focusConnectButton();
Expand All @@ -361,20 +390,31 @@ function Footer( {
{ __( 'Cancel' ) }
</Button>
<Button id="connect-button" variant="primary" disabled={ disabled } onClick={ onConnect }>
{ __( 'Connect' ) }
{ getButtonText() }
</Button>
</div>
</div>
);
}

const SyncSitesOfflineView = () => {
const offlineMessage = __( 'Connecting a site requires an internet connection.' );
const SyncSitesOfflineView = ( { mode = 'connect' }: { mode?: SyncModalMode } ) => {
const { __ } = useI18n();
const getOfflineMessage = () => {
switch ( mode ) {
case 'push':
return __( 'Publishing your site requires an internet connection.' );
case 'pull':
return __( 'Importing a remote site requires an internet connection.' );
case 'connect':
default:
return __( 'Connecting a site requires an internet connection.' );
}
};

return (
<div className="flex items-center justify-center h-12 px-2 pt-4 text-a8c-gray-70 gap-1">
<Icon className="m-1 fill-a8c-gray-70" size={ 24 } icon={ offlineIcon } />
<span className="text-[13px] leading-[16px]">{ offlineMessage }</span>
<span className="text-[13px] leading-[16px]">{ getOfflineMessage() }</span>
</div>
);
};
Loading
Loading