Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/components/add-site.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export default function AddSite( { className }: AddSiteProps ) {
);

const { data: versions = [] } = useGetWordPressVersions();
const latestStableVersion = versions.find( ( version ) => version.isLatest );
const latestStableVersion = versions.find( ( version ) => version.value === 'latest' );

const initializeForm = useCallback( async () => {
const siteName = await generateSiteName( sites );
Expand Down
2 changes: 1 addition & 1 deletion src/components/onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default function Onboarding() {
} );

const { data: versions = [] } = useGetWordPressVersions();
const latestStableVersion = versions.find( ( version ) => version.isLatest );
const latestStableVersion = versions.find( ( version ) => version.value === 'latest' );

useEffect( () => {
if ( latestStableVersion ) {
Expand Down
52 changes: 9 additions & 43 deletions src/components/site-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ import { useI18n } from '@wordpress/react-i18n';
import { FormEvent, useRef, useState } from 'react';
import Button from 'src/components/button';
import FolderIcon from 'src/components/folder-icon';
import offlineIcon from 'src/components/offline-icon';
import TextControlComponent from 'src/components/text-control';
import { Tooltip } from 'src/components/tooltip';
import { WPVersionSelector } from 'src/components/wp-version-selector';
import { ACCEPTED_IMPORT_FILE_TYPES } from 'src/constants';
import { useDocsLink } from 'src/hooks/use-docs-link';
import { useOffline } from 'src/hooks/use-offline';
import { isWindows } from 'src/lib/app-globals';
import { cx } from 'src/lib/cx';
import { generateCustomDomainFromSiteName } from 'src/lib/domains';
import { getIpcApi } from 'src/lib/get-ipc-api';
import { useGetWordPressVersions } from 'src/stores/wordpress-versions-api';
import {
DEFAULT_WORDPRESS_VERSION,
ALLOWED_PHP_VERSIONS,
Expand Down Expand Up @@ -267,15 +264,10 @@ export const SiteForm = ( {
}: SiteFormProps ) => {
const { __, isRTL } = useI18n();
const getDocsLink = useDocsLink();
const isOffline = useOffline();

const shouldShowCustomDomainError = useCustomDomain && customDomainError;
const errorCount = [ error, shouldShowCustomDomainError ].filter( Boolean ).length;

const offlineMessage = __( 'Changing WordPress version requires an internet connection.' );

const { data: wpVersions = [] } = useGetWordPressVersions();

const [ isAdvancedSettingsVisible, setAdvancedSettingsVisible ] = useState( false );

const handleAdvancedSettingsClick = () => {
Expand Down Expand Up @@ -412,40 +404,14 @@ export const SiteForm = ( {
__nextHasNoMarginBottom
/>
</div>
<div className="flex flex-col gap-1.5 leading-4">
<label className="font-semibold" htmlFor="wp-version-select">
{ __( 'WordPress version' ) }
</label>
<Tooltip
disabled={ ! isOffline }
icon={ offlineIcon }
text={ offlineMessage }
placement="top-start"
className="flex flex-1 flex-col"
>
<SelectControl
id="wp-version-select"
value={ wpVersion }
options={
wpVersions.length > 0
? wpVersions.map( ( { label, value } ) => ( {
label,
value,
} ) )
: [
{
label: DEFAULT_WORDPRESS_VERSION,
value: DEFAULT_WORDPRESS_VERSION,
},
]
}
onChange={ setWpVersion }
disabled={ isOffline }
__next40pxDefaultSize
__nextHasNoMarginBottom
/>
</Tooltip>
</div>

<WPVersionSelector

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! A single component 💪

selectedValue={ wpVersion }
onChange={ setWpVersion }
fallbackOptions={ [
{ label: __( 'Latest' ), value: DEFAULT_WORDPRESS_VERSION },
] }
/>
</div>

{ setUseCustomDomain && setCustomDomain && (
Expand Down
7 changes: 3 additions & 4 deletions src/components/tests/add-site.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@ jest.mock( 'src/stores/wordpress-versions-api', () => ( {
value: '6.5.0-beta1',
isBeta: true,
isDevelopment: false,
isLatest: false,
label: '6.5.0-beta1',
},
{ value: '6.4.0', isBeta: false, isDevelopment: false, isLatest: true, label: '6.4' },
{ value: '6.3.3', isBeta: false, isDevelopment: false, isLatest: false, label: '6.3.3' },
{ value: '6.4.0', isBeta: false, isDevelopment: false, label: '6.4' },
{ value: '6.3.3', isBeta: false, isDevelopment: false, label: '6.3.3' },
],
} ),
selectWordPressVersionsWithLatest: jest.fn(),
Expand Down Expand Up @@ -144,7 +143,7 @@ describe( 'AddSite', () => {
expect( mockCreateSite ).toHaveBeenCalledWith(
'test',
'My WordPress Website',
expect.any( String ),
'latest',
undefined,
false,
expect.any( Function )
Expand Down
6 changes: 3 additions & 3 deletions src/components/tests/onboarding.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ jest.mock( 'src/hooks/use-offline', () => ( {
jest.mock( 'src/stores/wordpress-versions-api', () => ( {
useGetWordPressVersions: jest.fn( () => ( {
data: [
{ isBeta: false, isDevelopment: false, isLatest: true, label: '6.3', value: '6.3.3' },
{ isBeta: false, isDevelopment: false, isLatest: false, label: '6.2', value: '6.2.0' },
{ isBeta: false, isDevelopment: false, isLatest: false, label: '6.1', value: '6.1.7' },
{ isBeta: false, isDevelopment: false, label: '6.3', value: '6.3.3' },
{ isBeta: false, isDevelopment: false, label: '6.2', value: '6.2.0' },
{ isBeta: false, isDevelopment: false, label: '6.1', value: '6.1.7' },
],
isLoading: false,
} ) ),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { addWpVersionToList } from 'src/modules/site-settings/lib/wordpress-versions';
import { addWpVersionToList } from './add-wp-version-to-list';

describe( 'addWpVersionToList', () => {
it( 'should add version to empty list', () => {
const options: Array< { label: string; value: string } > = [];
addWpVersionToList( '6.4.2', options );
expect( options ).toEqual( [ { label: '6.4.2', value: '6.4.2' } ] );
const result = addWpVersionToList( { label: '6.4.2', value: '6.4.2' }, options );
expect( result ).toEqual( [ { label: '6.4.2', value: '6.4.2' } ] );
} );

it( 'should add version at the top when it is newer than all existing versions', () => {
const options = [
{ label: '6.4.1', value: '6.4.1' },
{ label: '6.4.0', value: '6.4.0' },
];
addWpVersionToList( '6.4.3', options );
expect( options ).toEqual( [
const result = addWpVersionToList( { label: '6.4.3', value: '6.4.3' }, options );
expect( result ).toEqual( [
{ label: '6.4.3', value: '6.4.3' },
{ label: '6.4.1', value: '6.4.1' },
{ label: '6.4.0', value: '6.4.0' },
Expand All @@ -25,8 +25,8 @@ describe( 'addWpVersionToList', () => {
{ label: '6.4.2', value: '6.4.2' },
{ label: '6.4.1', value: '6.4.1' },
];
addWpVersionToList( '6.4.0', options );
expect( options ).toEqual( [
const result = addWpVersionToList( { label: '6.4.0', value: '6.4.0' }, options );
expect( result ).toEqual( [
{ label: '6.4.2', value: '6.4.2' },
{ label: '6.4.1', value: '6.4.1' },
{ label: '6.4.0', value: '6.4.0' },
Expand All @@ -38,8 +38,8 @@ describe( 'addWpVersionToList', () => {
{ label: '6.4.2', value: '6.4.2' },
{ label: '6.4.0', value: '6.4.0' },
];
addWpVersionToList( '6.4.1', options );
expect( options ).toEqual( [
const result = addWpVersionToList( { label: '6.4.1', value: '6.4.1' }, options );
expect( result ).toEqual( [
{ label: '6.4.2', value: '6.4.2' },
{ label: '6.4.1', value: '6.4.1' },
{ label: '6.4.0', value: '6.4.0' },
Expand All @@ -51,8 +51,8 @@ describe( 'addWpVersionToList', () => {
{ label: '6.4.2', value: '6.4.2' },
{ label: '6.4.0', value: '6.4.0' },
];
addWpVersionToList( 'nightly', options );
expect( options ).toEqual( [
const result = addWpVersionToList( { label: 'nightly', value: 'nightly' }, options );
expect( result ).toEqual( [
{ label: '6.4.2', value: '6.4.2' },
{ label: '6.4.0', value: '6.4.0' },
{ label: 'nightly', value: 'nightly' },
Expand All @@ -64,8 +64,8 @@ describe( 'addWpVersionToList', () => {
{ label: 'nightly', value: 'nightly' },
{ label: '6.4.0', value: '6.4.0' },
];
addWpVersionToList( '6.4.1', options );
expect( options ).toEqual( [
const result = addWpVersionToList( { label: '6.4.1', value: '6.4.1' }, options );
expect( result ).toEqual( [
{ label: 'nightly', value: 'nightly' },
{ label: '6.4.1', value: '6.4.1' },
{ label: '6.4.0', value: '6.4.0' },
Expand All @@ -77,8 +77,8 @@ describe( 'addWpVersionToList', () => {
{ label: '6.4.2', value: '6.4.2' },
{ label: '6.4.0', value: '6.4.0' },
];
addWpVersionToList( '6.4.1-beta2', options );
expect( options ).toEqual( [
const result = addWpVersionToList( { label: '6.4.1-beta2', value: '6.4.1-beta2' }, options );
expect( result ).toEqual( [
{ label: '6.4.2', value: '6.4.2' },
{ label: '6.4.1-beta2', value: '6.4.1-beta2' },
{ label: '6.4.0', value: '6.4.0' },
Expand All @@ -90,8 +90,8 @@ describe( 'addWpVersionToList', () => {
{ label: '6.4.2', value: '6.4.2' },
{ label: '6.4.0', value: '6.4.0' },
];
addWpVersionToList( '6.4.1-RC1', options );
expect( options ).toEqual( [
const result = addWpVersionToList( { label: '6.4.1-RC1', value: '6.4.1-RC1' }, options );
expect( result ).toEqual( [
{ label: '6.4.2', value: '6.4.2' },
{ label: '6.4.1-RC1', value: '6.4.1-RC1' },
{ label: '6.4.0', value: '6.4.0' },
Expand All @@ -104,8 +104,8 @@ describe( 'addWpVersionToList', () => {
{ label: '6.4.1-beta2', value: '6.4.1-beta2' },
{ label: '6.4.0', value: '6.4.0' },
];
addWpVersionToList( '6.4.1-beta1', options );
expect( options ).toEqual( [
const result = addWpVersionToList( { label: '6.4.1-beta1', value: '6.4.1-beta1' }, options );
expect( result ).toEqual( [
{ label: '6.4.2', value: '6.4.2' },
{ label: '6.4.1-beta2', value: '6.4.1-beta2' },
{ label: '6.4.1-beta1', value: '6.4.1-beta1' },
Expand Down
27 changes: 27 additions & 0 deletions src/components/wp-version-selector/add-wp-version-to-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import semver from 'semver';

type Option = { label: string; value: string };

export const addWpVersionToList = ( newOption: Option, options: Option[] ): Option[] => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I rewrote this function from mutability to immutability.
  2. Moved close to wpVersionsSelector, since it doesn';t make sense to keep it globally now, when we have only one pleace where we use it.
const currentVer = semver.coerce( newOption.value );

// Being extra cautious here, if the version is not valid, we add it to the end of the list.
if ( ! currentVer ) {
return [ ...options, newOption ];
}

const firstOlderVersionIndex = options.findIndex( ( compareVersion ) => {
const compareVer = semver.coerce( compareVersion.value );
return compareVer && semver.gt( currentVer, compareVer );
} );

if ( firstOlderVersionIndex === -1 ) {
return [ ...options, newOption ];
} else {
return [
...options.slice( 0, firstOlderVersionIndex ),
newOption,
...options.slice( firstOlderVersionIndex ),
];
}
};
121 changes: 121 additions & 0 deletions src/components/wp-version-selector/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { SelectControl, Icon } from '@wordpress/components';

@nightnei nightnei Apr 10, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we have separate component which doesn't store any context, so it's already used in 2 places and can be easily reused in the future, w/o extra conditions.
Also now we have consistency with rendering it for creating/updating website, so it works the same way, we don't need to copy-paste etc.

import { warning } from '@wordpress/icons';
import { useI18n } from '@wordpress/react-i18n';
import offlineIcon from 'src/components/offline-icon';
import { Tooltip } from 'src/components/tooltip';
import { useOffline } from 'src/hooks/use-offline';
import { cx } from 'src/lib/cx';
import { isWordPressDevVersion } from 'src/lib/version-utils';
import { isWordPressBetaVersion } from 'src/lib/wordpress-version-utils';
import { useGetWordPressVersions } from 'src/stores/wordpress-versions-api';
import { DEFAULT_WORDPRESS_VERSION } from 'vendor/wp-now/src/constants';
import { addWpVersionToList } from './add-wp-version-to-list';

type WPVersionSelectorProps = {
selectedValue: string;
onChange: ( version: string ) => void;
errorMessage?: string | null;
disabled?: boolean;
/** Is used if you want to add a custom option to the list, for example the current version of a site */
extraOptions?: { label: string; value: string }[];
/** Fallback options shown when available versions couldn't be fetched */
fallbackOptions: { label: string; value: string }[];
};

export const WPVersionSelector = ( {
selectedValue,
onChange,
errorMessage,
disabled = false,
extraOptions,
fallbackOptions,
}: WPVersionSelectorProps ) => {
const { __ } = useI18n();
const isOffline = useOffline();
const offlineMessage = __( 'Changing WordPress version requires an internet connection.' );
const { data: wpVersions = [] } = useGetWordPressVersions();

let betaVersions: { label: string; value: string }[] = wpVersions.filter(
( version ) => version.isBeta || version.isDevelopment
);
let stableVersions: { label: string; value: string }[] = wpVersions.filter(
( version ) => ! version.isBeta && ! version.isDevelopment && version.value !== 'latest'
);
extraOptions?.forEach( ( extraOption ) => {
const alreadyExists = wpVersions.some( ( version ) => version.value === extraOption.value );
if ( alreadyExists ) {
return;
}

if (
isWordPressBetaVersion( extraOption.value ) ||
isWordPressDevVersion( extraOption.value )
) {
betaVersions = addWpVersionToList( extraOption, betaVersions );
} else {
stableVersions = addWpVersionToList( extraOption, stableVersions );
}
} );

return (
<label className="flex flex-1 flex-col gap-1.5 leading-4">
<span className="font-semibold flex items-center gap-2">
{ __( 'WordPress version' ) }
{ selectedValue !== 'latest' && (
<Tooltip
text={ __( 'WordPress Core automatic updates will be disabled for this site.' ) }
placement="top"
>
<Icon icon={ warning } size={ 16 } />
</Tooltip>
) }
</span>
<Tooltip
disabled={ ! isOffline }
icon={ offlineIcon }
text={ offlineMessage }
placement="top-start"
className="flex flex-1 flex-col"
>
<SelectControl
className={ cx( errorMessage && 'error-select-control' ) }
disabled={ disabled || isOffline }
value={ selectedValue }
onChange={ onChange }
__next40pxDefaultSize
__nextHasNoMarginBottom
>
{ wpVersions.length > 0 ? (
<>
<optgroup label={ __( 'Auto-updating' ) }>
<option key={ DEFAULT_WORDPRESS_VERSION } value={ DEFAULT_WORDPRESS_VERSION }>
{ __( 'latest' ) }
</option>
</optgroup>
<optgroup label={ __( 'Beta & Nightly' ) }>
{ betaVersions.map( ( { label, value } ) => (
<option key={ value } value={ value }>
{ label }
</option>
) ) }
</optgroup>
<optgroup label={ __( 'Stable Versions' ) }>
{ stableVersions.map( ( { label, value } ) => (
<option key={ value } value={ value }>
{ label }
</option>
) ) }
</optgroup>
</>
) : (
fallbackOptions.map( ( { label, value } ) => (
<option key={ value } value={ value }>
{ label }
</option>
) )
) }
</SelectControl>
</Tooltip>
</label>
);
};
Loading