Skip to content

Commit

Permalink
Preview Sites: Add Clear button for expired sites with related styl…
Browse files Browse the repository at this point in the history
…ing (#913)

* Add `Clear` button for expired Preview sites

* Update styling

* Add tests

* Update `Clear` button color to `text-a8c-blueberry`

* Update expired site text to `text-[#757575]`

* Sort preview sites to display the latest updated at the top of the list

* Add a new variable for the `#757575` color to tailwind config file

---------

Co-authored-by: Antonio Sejas <antonio@sejas.es>
  • Loading branch information
ivan-ottinger and sejas authored Feb 11, 2025
1 parent af558c5 commit 0428c01
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/components/content-tab-previews.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ export function ContentTabPreviews( { selectedSite }: ContentTabPreviewsProps )
) }
{ snapshotsOnSite
.filter( ( snapshot ) => ! snapshot.isLoading )
.sort( ( a, b ) => b.date - a.date )
.map( ( snapshot ) => (
<PreviewSiteRow
snapshot={ snapshot }
Expand Down
2 changes: 1 addition & 1 deletion src/components/user-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const UserInfo = ( {
</Button>
<div className="flex flex-col">
<span className="overflow-ellipsis">{ user?.displayName }</span>
<span className="text-[#757575] text-[10px] leading-[10px]">{ user?.email }</span>
<span className="text-a8c-gray-700 text-[10px] leading-[10px]">{ user?.email }</span>
</div>
</div>
<Button variant="secondary" className="!gap-3" onClick={ onLogout }>
Expand Down
56 changes: 38 additions & 18 deletions src/modules/preview-site/components/preview-site-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useExpirationDate } from 'src/hooks/use-expiration-date';
import { useFormatLocalizedTimestamps } from 'src/hooks/use-format-localized-timestamps';
import { useSnapshots } from 'src/hooks/use-snapshots';
import { useUpdateDemoSite } from 'src/hooks/use-update-demo-site';
import { cx } from 'src/lib/cx';
import { getIpcApi } from 'src/lib/get-ipc-api';
import { PreviewActionButtonsMenu } from 'src/modules/preview-site/components/preview-action-buttons-menu';
import { ProgressRow } from 'src/modules/preview-site/components/progress-row';
Expand All @@ -32,8 +33,8 @@ export function PreviewSiteRow( {
}: PreviewSiteRowProps ) {
const { __ } = useI18n();
const { url, date, isDeleting } = snapshot;
const { countDown } = useExpirationDate( date );
const { fetchSnapshotUsage } = useSnapshots();
const { countDown, isExpired } = useExpirationDate( date );
const { fetchSnapshotUsage, removeSnapshot } = useSnapshots();
const { isDemoSiteUpdating } = useUpdateDemoSite();
const isPreviewSiteUpdating = isDemoSiteUpdating( snapshot.atomicSiteId );
const { formatRelativeTime } = useFormatLocalizedTimestamps();
Expand Down Expand Up @@ -93,24 +94,33 @@ export function PreviewSiteRow( {
<div className="flex items-center px-8 py-6">
<div className="w-[51%]">
<div className="flex items-center">
<div className="text-[13px] leading-5 line-clamp-1 break-all">
<div
className={ cx(
'text-[13px] leading-5 line-clamp-1 break-all',
isExpired && 'line-through text-a8c-gray-700'
) }
>
{ /* translators: %s: Site name (e.g. "My Site Preview") */ }
{ snapshot.name || sprintf( __( '%s Preview' ), selectedSite.name ) }
</div>
</div>
<Button
variant="link"
className="!text-a8c-gray-70 hover:!text-a8c-blueberry max-w-[100%]"
onClick={ () => {
getIpcApi().openURL( urlWithHTTPS );
} }
disabled={ isExpired }
className={ cx(
'!text-a8c-gray-700 max-w-[100%]',
isExpired ? 'pointer-events-none' : 'hover:!text-a8c-blueberry'
) }
onClick={ () => getIpcApi().openURL( urlWithHTTPS ) }
>
<span className="truncate">{ urlWithHTTPS }</span>
<ArrowIcon />
<span className={ cx( 'truncate', isExpired && 'line-through text-a8c-gray-700' ) }>
{ urlWithHTTPS }
</span>
{ ! isExpired && <ArrowIcon /> }
</Button>
</div>
<div className="flex ml-auto">
<div className="w-[110px] text-[#757575] flex items-center pl-4">
<div className="w-[110px] text-a8c-gray-700 flex items-center pl-4">
{ isPreviewSiteUpdating ? (
<div className="flex items-center text-gray-900">
<Spinner className="!mt-0 !mx-2" />
Expand All @@ -120,15 +130,25 @@ export function PreviewSiteRow( {
getLastUpdateTimeText()
) }
</div>
<div className="w-[100px] text-[#757575] flex items-center pl-4">{ countDown }</div>
<div className="w-[100px] text-a8c-gray-700 flex items-center pl-4">{ countDown }</div>
<div className="w-[60px] flex justify-end">
<PreviewActionButtonsMenu
snapshot={ snapshot }
selectedSite={ selectedSite }
disabledUpdate={ disabledUpdate }
updateButtonTooltipContent={ updateButtonTooltipContent }
showUpdateTooltip={ showUpdateTooltip }
/>
{ isExpired ? (
<Button
variant="link"
onClick={ () => removeSnapshot( snapshot ) }
className={ '!text-a8c-blueberry hover:!text-a8c-red-50' }
>
{ __( 'Clear' ) }
</Button>
) : (
<PreviewActionButtonsMenu
snapshot={ snapshot }
selectedSite={ selectedSite }
disabledUpdate={ disabledUpdate }
updateButtonTooltipContent={ updateButtonTooltipContent }
showUpdateTooltip={ showUpdateTooltip }
/>
) }
</div>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/modules/preview-site/components/progress-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export function ProgressRow( { text }: ProgressRowProps ) {
</div>
</div>
<div className="flex ml-auto">
<div className="w-[110px] text-[#757575] flex items-center pl-4">{ '-' }</div>
<div className="w-[100px] text-[#757575] flex items-center pl-4">{ '-' }</div>
<div className="w-[110px] text-a8c-gray-700 flex items-center pl-4">{ '-' }</div>
<div className="w-[100px] text-a8c-gray-700 flex items-center pl-4">{ '-' }</div>
<div className="w-[60px] pr-2" />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { render, screen } from '@testing-library/react';
import { useExpirationDate } from 'src/hooks/use-expiration-date';
import { PreviewSiteRow } from '../preview-site-row';

jest.mock( 'src/hooks/use-snapshots', () => ( {
useSnapshots: jest.fn().mockReturnValue( {
removeSnapshot: jest.fn(),
fetchSnapshotUsage: jest.fn(),
} ),
} ) );

jest.mock( 'src/hooks/use-expiration-date', () => ( {
useExpirationDate: jest.fn().mockReturnValue( {
countDown: '5 days',
isExpired: false,
} ),
} ) );

jest.mock( 'src/hooks/use-format-localized-timestamps', () => ( {
useFormatLocalizedTimestamps: jest.fn().mockReturnValue( {
formatRelativeTime: jest.fn().mockReturnValue( '2 hours' ),
} ),
} ) );

describe( 'PreviewSiteRow', () => {
const mockSnapshot = {
atomicSiteId: 123,
localSiteId: 'db30ac2b-1d8f-4df2-a171-1b9ea3bc149d',
url: 'shad-of-cellos.wp.build',
date: 123456789,
name: 'Test Preview 1',
sequence: 1,
};

const mockSelectedSite: StoppedSiteDetails = {
id: '456',
name: 'Test',
path: '/test/path',
phpVersion: '8.2',
running: false,
};

beforeEach( () => {
jest.clearAllMocks();
} );

it( 'renders PreviewActionButtonsMenu when preview site is not expired', () => {
render(
<PreviewSiteRow
snapshot={ mockSnapshot }
selectedSite={ mockSelectedSite }
disabledUpdate={ false }
/>
);

expect( screen.getByRole( 'button', { name: 'Preview actions' } ) ).toBeInTheDocument();
expect( screen.queryByRole( 'button', { name: 'Clear' } ) ).not.toBeInTheDocument();
} );

it( 'renders Clear button instead of PreviewActionButtonsMenu when preview site is expired', () => {
( useExpirationDate as jest.Mock ).mockReturnValueOnce( {
countDown: 'Expired',
isExpired: true,
} );

render(
<PreviewSiteRow
snapshot={ mockSnapshot }
selectedSite={ mockSelectedSite }
disabledUpdate={ false }
/>
);

expect( screen.queryByRole( 'button', { name: 'Preview actions' } ) ).not.toBeInTheDocument();
expect( screen.getByRole( 'button', { name: 'Clear' } ) ).toBeInTheDocument();
} );

it( 'applies line-through style when preview site is expired', () => {
( useExpirationDate as jest.Mock ).mockReturnValueOnce( {
countDown: 'Expired',
isExpired: true,
} );

render(
<PreviewSiteRow
snapshot={ mockSnapshot }
selectedSite={ mockSelectedSite }
disabledUpdate={ false }
/>
);

const siteName = screen.getByText( mockSnapshot.name );
const siteUrl = screen.getByText( `https://${ mockSnapshot.url }` );

expect( siteName.className ).toContain( 'line-through' );
expect( siteUrl.className ).toContain( 'line-through' );
} );
} );
3 changes: 2 additions & 1 deletion tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,12 @@ for ( const [ key, value ] of Object.entries( palette.colors ) ) {
a8cToTailwindColors[ colorName ][ shade ] = value;
}

// This colors are in the palette but not included because the color name contains more than one word.
// These colors are in the palette but not included because the color name contains more than one word.
// Reference: https://github.com/Automattic/color-studio/blob/55218ffdaecc770cd697639071f1d2083f744f66/dist/colors.json#L123-L187
a8cToTailwindColors[ `${ PREFIX }-blueberry-5` ] = '#F7F8FE'; // WordPress Blue 5
a8cToTailwindColors[ `${ PREFIX }-blueberry` ] = '#3858E9'; // WordPress Blue
a8cToTailwindColors[ `${ PREFIX }-blueberry-70` ] = '#1d35b4'; // WordPress Blue 70
a8cToTailwindColors[ `${ PREFIX }-gray-700` ] = '#757575'; // Gray 700

module.exports = {
content: [ './src/**/*.{html,ejs,js,jsx,ts,tsx}' ],
Expand Down

0 comments on commit 0428c01

Please sign in to comment.