};
};
-const agencyDashboardSortToQueryObject = ( sort: DashboardSortInterface ) => {
+const agencyDashboardSortToQueryObject = ( sort?: DashboardSortInterface ) => {
+ if ( ! sort ) {
+ return;
+ }
+
return {
...( sort?.field && { sort_field: sort.field } ),
...( sort?.direction && { sort_direction: sort.direction } ),
@@ -33,10 +37,10 @@ const agencyDashboardSortToQueryObject = ( sort: DashboardSortInterface ) => {
export interface FetchDashboardSitesArgsInterface {
isPartnerOAuthTokenLoaded: boolean;
- searchQuery: string;
+ searchQuery: string | undefined;
currentPage: number;
filter: AgencyDashboardFilter;
- sort: DashboardSortInterface;
+ sort?: DashboardSortInterface;
perPage?: number;
agencyId?: number;
}
diff --git a/client/data/paid-newsletter/use-map-stripe-plan-to-product-mutation.ts b/client/data/paid-newsletter/use-map-stripe-plan-to-product-mutation.ts
index 38c0a939574c87..b847fc6ca96d2f 100644
--- a/client/data/paid-newsletter/use-map-stripe-plan-to-product-mutation.ts
+++ b/client/data/paid-newsletter/use-map-stripe-plan-to-product-mutation.ts
@@ -8,7 +8,7 @@ import { useCallback } from 'react';
import wp from 'calypso/lib/wp';
interface MutationVariables {
- siteId: string;
+ siteId: number;
engine: string;
currentStep: string;
stripePlan: string;
@@ -48,10 +48,8 @@ export const useMapStripePlanToProductMutation = (
},
...options,
onSuccess( ...args ) {
- const [ , { siteId, engine, currentStep } ] = args;
- queryClient.invalidateQueries( {
- queryKey: [ 'paid-newsletter-importer', siteId, engine, currentStep ],
- } );
+ const [ data, { siteId, engine } ] = args;
+ queryClient.setQueryData( [ 'paid-newsletter-importer', siteId, engine ], data );
options.onSuccess?.( ...args );
},
} );
@@ -60,7 +58,7 @@ export const useMapStripePlanToProductMutation = (
const mapStripePlanToProduct = useCallback(
(
- siteId: string,
+ siteId: number,
engine: string,
currentStep: string,
stripePlan: string,
diff --git a/client/data/paid-newsletter/use-paid-newsletter-query.ts b/client/data/paid-newsletter/use-paid-newsletter-query.ts
index 184b9b699c3f2f..91912777789ce1 100644
--- a/client/data/paid-newsletter/use-paid-newsletter-query.ts
+++ b/client/data/paid-newsletter/use-paid-newsletter-query.ts
@@ -1,20 +1,82 @@
import { keepPreviousData, useQuery } from '@tanstack/react-query';
import wp from 'calypso/lib/wp';
-interface PaidNewsletterStep {
- status: string;
- content?: any;
+export type StepId = 'content' | 'subscribers' | 'paid-subscribers' | 'summary';
+export type StepStatus = 'initial' | 'skipped' | 'importing' | 'done';
+
+export interface ContentStepContent {}
+
+export interface SubscribersStepContent {
+ meta?: {
+ email_count: string;
+ id: number;
+ paid_subscribers_count: string;
+ platform: string;
+ scheduled_at: string;
+ status: string;
+ subscribed_count: string | null;
+ timestamp: string;
+ };
+}
+
+export interface Product {
+ currency: string;
+ id: number;
+ interval: string;
+ price: string;
+ title: string;
+}
+
+export interface Plan {
+ active_subscriptions: boolean;
+ is_active: boolean;
+ name: string;
+ plan_amount_decimal: number;
+ plan_currency: string;
+ plan_id: string;
+ plan_interval: string;
+ product_id: string;
+}
+
+export interface PaidSubscribersStepContent {
+ available_tiers: Product[];
+ connect_url?: string;
+ is_connected_stripe: boolean;
+ map_plans: Record< string, string >;
+ plans: Plan[];
+}
+
+export interface SummaryStepContent {}
+
+interface Step< T > {
+ status: StepStatus;
+ content?: T;
+}
+
+interface Steps {
+ content: Step< ContentStepContent >;
+ subscribers: Step< SubscribersStepContent >;
+ 'paid-subscribers': Step< PaidSubscribersStepContent >;
+ summary: Step< SummaryStepContent >;
}
interface PaidNewsletterData {
- current_step: string;
- steps: Record< string, PaidNewsletterStep >;
+ current_step: StepId;
+ steps: Steps;
}
-export const usePaidNewsletterQuery = ( engine: string, currentStep: string, siteId?: number ) => {
+const REFRESH_INTERVAL = 2000; // every 2 seconds.
+
+export const usePaidNewsletterQuery = (
+ engine: string,
+ currentStep: StepId,
+ siteId?: number,
+ autoRefresh?: boolean
+) => {
return useQuery( {
enabled: !! siteId,
- queryKey: [ 'paid-newsletter-importer', siteId, engine, currentStep ],
+ // eslint-disable-next-line @tanstack/query/exhaustive-deps
+ queryKey: [ 'paid-newsletter-importer', siteId, engine ],
queryFn: (): Promise< PaidNewsletterData > => {
return wp.req.get(
{
@@ -30,5 +92,6 @@ export const usePaidNewsletterQuery = ( engine: string, currentStep: string, sit
placeholderData: keepPreviousData,
refetchOnWindowFocus: true,
staleTime: 6000, // 10 minutes
+ refetchInterval: autoRefresh ? REFRESH_INTERVAL : false,
} );
};
diff --git a/client/data/paid-newsletter/use-reset-mutation.ts b/client/data/paid-newsletter/use-reset-mutation.ts
index a23caa753ae686..5f58327ff6b352 100644
--- a/client/data/paid-newsletter/use-reset-mutation.ts
+++ b/client/data/paid-newsletter/use-reset-mutation.ts
@@ -38,10 +38,9 @@ export const useResetMutation = (
},
...options,
onSuccess( ...args ) {
- const [ , { siteId, engine, currentStep } ] = args;
- queryClient.invalidateQueries( {
- queryKey: [ 'paid-newsletter-importer', siteId, engine, currentStep ],
- } );
+ const [ data, { siteId, engine } ] = args;
+
+ queryClient.setQueryData( [ 'paid-newsletter-importer', siteId, engine ], data );
options.onSuccess?.( ...args );
},
} );
diff --git a/client/data/paid-newsletter/use-skip-next-step-mutation.ts b/client/data/paid-newsletter/use-skip-next-step-mutation.ts
index d602be6ef59405..50976016f23812 100644
--- a/client/data/paid-newsletter/use-skip-next-step-mutation.ts
+++ b/client/data/paid-newsletter/use-skip-next-step-mutation.ts
@@ -20,6 +20,16 @@ export const useSkipNextStepMutation = (
const queryClient = useQueryClient();
const mutation = useMutation( {
mutationFn: async ( { siteId, engine, currentStep, skipStep }: MutationVariables ) => {
+ queryClient.setQueryData(
+ [ 'paid-newsletter-importer', siteId, engine ],
+ ( previous: any ) => {
+ const optimisticData = previous; // { steps: [ { [ skipStep ]: { status: 'skipped' } } ] };
+
+ optimisticData.steps[ skipStep ].status = 'skipped';
+ return optimisticData;
+ }
+ );
+
const response = await wp.req.post(
{
path: `/sites/${ siteId }/site-importer/paid-newsletter`,
@@ -40,10 +50,8 @@ export const useSkipNextStepMutation = (
},
...options,
onSuccess( ...args ) {
- const [ , { siteId, engine, currentStep } ] = args;
- queryClient.invalidateQueries( {
- queryKey: [ 'paid-newsletter-importer', siteId, engine, currentStep ],
- } );
+ const [ data, { siteId, engine } ] = args;
+ queryClient.setQueryData( [ 'paid-newsletter-importer', siteId, engine ], data );
options.onSuccess?.( ...args );
},
} );
diff --git a/client/data/paid-newsletter/use-subscriber-import-mutation.ts b/client/data/paid-newsletter/use-subscriber-import-mutation.ts
new file mode 100644
index 00000000000000..210f38e173beaf
--- /dev/null
+++ b/client/data/paid-newsletter/use-subscriber-import-mutation.ts
@@ -0,0 +1,56 @@
+import {
+ DefaultError,
+ useMutation,
+ UseMutationOptions,
+ useQueryClient,
+} from '@tanstack/react-query';
+import { useCallback } from 'react';
+import wp from 'calypso/lib/wp';
+
+interface MutationVariables {
+ siteId: number;
+ engine: string;
+ currentStep: string;
+}
+
+export const useSubscriberImportMutation = (
+ options: UseMutationOptions< unknown, DefaultError, MutationVariables > = {}
+) => {
+ const queryClient = useQueryClient();
+ const mutation = useMutation( {
+ mutationFn: async ( { siteId, engine, currentStep }: MutationVariables ) => {
+ const response = await wp.req.post(
+ {
+ path: `/sites/${ siteId }/site-importer/paid-newsletter/subscriber-import`,
+ apiNamespace: 'wpcom/v2',
+ },
+ {
+ engine: engine,
+ current_step: currentStep,
+ }
+ );
+
+ if ( ! response.current_step ) {
+ throw new Error( 'unsuccsefully skipped step', response );
+ }
+
+ return response;
+ },
+ ...options,
+ onSuccess( ...args ) {
+ const [ data, { siteId, engine } ] = args;
+ queryClient.setQueryData( [ 'paid-newsletter-importer', siteId, engine ], data );
+ options.onSuccess?.( ...args );
+ },
+ } );
+
+ const { mutate } = mutation;
+
+ const enqueueSubscriberImport = useCallback(
+ ( siteId: number, engine: string, currentStep: string ) =>
+ mutate( { siteId, engine, currentStep } ),
+ [ mutate ]
+ );
+
+ return { enqueueSubscriberImport, ...mutation };
+};
diff --git a/client/data/site-profiler/types.ts b/client/data/site-profiler/types.ts
index 5089fef55afaf8..da87237bc95669 100644
--- a/client/data/site-profiler/types.ts
+++ b/client/data/site-profiler/types.ts
@@ -112,6 +112,10 @@ export interface UrlBasicMetricsQueryResponse {
token: string;
}
+export interface LeadMutationResponse {
+ success: boolean;
+}
+
export interface UrlSecurityMetricsQueryResponse {
wpscan: {
report: {
diff --git a/client/data/site-profiler/use-lead-query.ts b/client/data/site-profiler/use-lead-query.ts
new file mode 100644
index 00000000000000..852a8fa07c10da
--- /dev/null
+++ b/client/data/site-profiler/use-lead-query.ts
@@ -0,0 +1,20 @@
+import { useMutation } from '@tanstack/react-query';
+import { LeadMutationResponse } from 'calypso/data/site-profiler/types';
+import wp from 'calypso/lib/wp';
+
+export const useLeadMutation = ( url?: string, hash?: string ) => {
+ return useMutation( {
+ mutationKey: [ 'lead', url, hash ],
+ mutationFn: (): Promise< LeadMutationResponse > =>
+ wp.req.post(
+ {
+ path: '/site-profiler/lead',
+ apiNamespace: 'wpcom/v2',
+ },
+ {
+ url,
+ hash,
+ }
+ ),
+ } );
+};
diff --git a/client/devdocs/design/index.jsx b/client/devdocs/design/index.jsx
index 066c358d7ce8c4..6b0202fa436aeb 100644
--- a/client/devdocs/design/index.jsx
+++ b/client/devdocs/design/index.jsx
@@ -121,7 +121,6 @@ import WpcomColophon from 'calypso/components/wpcom-colophon/docs/example';
import Collection from 'calypso/devdocs/design/search-collection';
import { slugToCamelCase } from 'calypso/devdocs/docs-example/util';
import SitesGridItemExample from 'calypso/sites-dashboard/components/sites-grid-item/docs/example';
-import SitesGridItemSelectExample from 'calypso/sites-dashboard/components/sites-grid-item-select/docs/example';
export default class DesignAssets extends Component {
static displayName = 'DesignAssets';
@@ -267,7 +266,6 @@ export default class DesignAssets extends Component {
-
diff --git a/client/gutenberg/editor/calypsoify-iframe.tsx b/client/gutenberg/editor/calypsoify-iframe.tsx
index d884a73439ed9a..6ecdbfede05b92 100644
--- a/client/gutenberg/editor/calypsoify-iframe.tsx
+++ b/client/gutenberg/editor/calypsoify-iframe.tsx
@@ -95,7 +95,7 @@ enum WindowActions {
enum EditorActions {
GoToAllPosts = 'goToAllPosts', // Unused action in favor of CloseEditor. Maintained here to support cached scripts.
- GoToPatterns = 'goToPatterns',
+ WpAdminRedirect = 'wpAdminRedirect',
CloseEditor = 'closeEditor',
OpenMediaModal = 'openMediaModal',
OpenCheckoutModal = 'openCheckoutModal',
@@ -383,7 +383,7 @@ class CalypsoifyIframe extends Component< ComponentProps, State > {
this.navigate( destinationUrl, unsavedChanges );
}
- if ( EditorActions.GoToPatterns === action ) {
+ if ( EditorActions.WpAdminRedirect === action ) {
const { destinationUrl, unsavedChanges } = payload;
this.navigate( `https://${ this.props.siteSlug }${ destinationUrl }`, unsavedChanges );
diff --git a/client/hosting/hosting-features/components/hosting-features.tsx b/client/hosting/hosting-features/components/hosting-features.tsx
index 39800ead7de6ff..41993442b4aba6 100644
--- a/client/hosting/hosting-features/components/hosting-features.tsx
+++ b/client/hosting/hosting-features/components/hosting-features.tsx
@@ -63,7 +63,8 @@ const HostingFeatures = () => {
// `siteTransferData?.isTransferring` is not a fully reliable indicator by itself, which is why
// we also look at `siteTransferData.status`
const isTransferInProgress =
- siteTransferData?.isTransferring || siteTransferData?.status === transferStates.COMPLETED;
+ ( siteTransferData?.isTransferring || siteTransferData?.status === transferStates.COMPLETED ) &&
+ ! isPlanExpired;
useEffect( () => {
if ( ! siteId ) {
diff --git a/client/hosting/overview/components/plan-card.tsx b/client/hosting/overview/components/plan-card.tsx
index 26be5d7af8c832..520dfbc1fff43e 100644
--- a/client/hosting/overview/components/plan-card.tsx
+++ b/client/hosting/overview/components/plan-card.tsx
@@ -127,8 +127,6 @@ const PricingSection: FC = () => {
{ isFreePlan && (
dispatch( recordTracksEvent( 'calypso_hosting_overview_upgrade_plan_click' ) )
diff --git a/client/hosting/overview/components/site-backup-card/index.js b/client/hosting/overview/components/site-backup-card/index.js
index b3b04138a52434..14f9a3e76fee0f 100644
--- a/client/hosting/overview/components/site-backup-card/index.js
+++ b/client/hosting/overview/components/site-backup-card/index.js
@@ -67,8 +67,6 @@ const SiteBackupCard = ( { lastGoodBackup, requestBackups, siteId, siteSlug } )
) }
* {
- flex-grow: 1;
- }
-
.dataviews-view-list {
flex: 1;
max-height: none;
- }
- .components-base-control {
- width: 85%;
+ // Ideally instead of reusing the site name full field
+ // We should be setting a media field and a primary field.
+ .dataviews-view-list__media-wrapper {
+ display: none;
+ }
+ .dataviews-view-list__primary-field {
+ display: none;
+ }
}
}
@@ -710,3 +689,11 @@
}
}
}
+
+.is-mobile-app-view {
+ .wpcom-site .a4a-layout.sites-dashboard .site-preview-pane {
+ .item-preview__header {
+ padding: 0;
+ }
+ }
+}
diff --git a/client/hosting/sites/components/site-preview-pane/dotcom-preview-pane.tsx b/client/hosting/sites/components/site-preview-pane/dotcom-preview-pane.tsx
index 15bc4e67f17eb7..43d826a764fe83 100644
--- a/client/hosting/sites/components/site-preview-pane/dotcom-preview-pane.tsx
+++ b/client/hosting/sites/components/site-preview-pane/dotcom-preview-pane.tsx
@@ -25,14 +25,15 @@ import type {
FeaturePreviewInterface,
} from 'calypso/a8c-for-agencies/components/items-dashboard/item-preview-pane/types';
-type Props = {
+interface Props {
site: SiteExcerptData;
selectedSiteFeature: string;
setSelectedSiteFeature: ( feature: string ) => void;
selectedSiteFeaturePreview: React.ReactNode;
closeSitePreviewPane: () => void;
changeSitePreviewPane: ( siteId: number ) => void;
-};
+ sectionName?: string;
+}
const OVERLAY_MODAL_SELECTORS = [
'body.modal-open',
@@ -40,6 +41,12 @@ const OVERLAY_MODAL_SELECTORS = [
'div.help-center__container:not(.is-minimized)',
];
+type HeaderButtonsProps = {
+ focusRef: React.RefObject< HTMLButtonElement >;
+ itemData: ItemData;
+ closeSitePreviewPane: () => void;
+};
+
const DotcomPreviewPane = ( {
site,
selectedSiteFeature,
@@ -186,7 +193,9 @@ const DotcomPreviewPane = ( {
itemPreviewPaneHeaderExtraProps={ {
externalIconSize: 16,
siteIconFallback: 'first-grapheme',
- headerButtons: PreviewPaneHeaderButtons,
+ headerButtons: ( props: HeaderButtonsProps ) => (
+
+ ),
subtitleExtra: () =>
( site.is_wpcom_staging_site || isStagingStatusFinished ) && (
diff --git a/client/hosting/sites/components/site-preview-pane/preview-pane-header-buttons.tsx b/client/hosting/sites/components/site-preview-pane/preview-pane-header-buttons.tsx
index 99de05de98aefc..dfa83cbc312e57 100644
--- a/client/hosting/sites/components/site-preview-pane/preview-pane-header-buttons.tsx
+++ b/client/hosting/sites/components/site-preview-pane/preview-pane-header-buttons.tsx
@@ -11,12 +11,19 @@ type Props = {
focusRef: React.RefObject< HTMLButtonElement >;
itemData: ItemData;
closeSitePreviewPane?: () => void;
+ sectionName?: string;
};
-const PreviewPaneHeaderButtons = ( { focusRef, closeSitePreviewPane, itemData }: Props ) => {
+const PreviewPaneHeaderButtons = ( {
+ focusRef,
+ closeSitePreviewPane,
+ itemData,
+ sectionName,
+}: Props ) => {
const adminButtonRef = useRef< HTMLButtonElement | null >( null );
const { adminLabel, adminUrl } = useSiteAdminInterfaceData( itemData.blogId );
const { __ } = useI18n();
+ const isHostingOverview = sectionName === 'dotcom-hosting';
return (
<>
@@ -24,7 +31,7 @@ const PreviewPaneHeaderButtons = ( { focusRef, closeSitePreviewPane, itemData }:
{ __( 'Close' ) }
{
+const SitesDashboardHeader: React.FC< SitesDashboardHeaderProps > = ( { isPreviewPaneOpen } ) => {
const { __ } = useI18n();
const isMobile = useMobileBreakpoint();
@@ -112,9 +116,9 @@ const SitesDashboardHeader = () => {
{
recordTracksEvent( 'calypso_sites_dashboard_new_site_action_click_add' );
diff --git a/client/hosting/sites/components/sites-dashboard.tsx b/client/hosting/sites/components/sites-dashboard.tsx
index 134dc46eea0113..8a2f68b26f0e41 100644
--- a/client/hosting/sites/components/sites-dashboard.tsx
+++ b/client/hosting/sites/components/sites-dashboard.tsx
@@ -8,6 +8,8 @@ import {
useSitesListSorting,
} from '@automattic/sites';
import { GroupableSiteLaunchStatuses } from '@automattic/sites/src/use-sites-list-grouping';
+import { DESKTOP_BREAKPOINT, WIDE_BREAKPOINT } from '@automattic/viewport';
+import { useBreakpoint } from '@automattic/viewport-react';
import clsx from 'clsx';
import { translate } from 'i18n-calypso';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
@@ -50,7 +52,7 @@ import { DOTCOM_OVERVIEW, FEATURE_TO_ROUTE_MAP } from './site-preview-pane/const
import DotcomPreviewPane from './site-preview-pane/dotcom-preview-pane';
import SitesDashboardHeader from './sites-dashboard-header';
import DotcomSitesDataViews, { useSiteStatusGroups } from './sites-dataviews';
-import { getSitesPagination, addDummyDataViewPrefix } from './sites-dataviews/utils';
+import { getSitesPagination } from './sites-dataviews/utils';
import type { SiteDetails } from '@automattic/data-stores';
// todo: we are using A4A styles until we extract them as common styles in the ItemsDashboard component
@@ -66,19 +68,16 @@ interface SitesDashboardProps {
selectedSite?: SiteDetails | null;
initialSiteFeature?: string;
selectedSiteFeaturePreview?: React.ReactNode;
+ sectionName?: string;
}
const siteSortingKeys = [
- // Put the dummy data view at the beginning for searching the sort key.
- { dataView: addDummyDataViewPrefix( 'site' ), sortKey: 'alphabetically' },
- { dataView: addDummyDataViewPrefix( 'last-publish' ), sortKey: 'updatedAt' },
- { dataView: addDummyDataViewPrefix( 'last-interacted' ), sortKey: 'lastInteractedWith' },
{ dataView: 'site', sortKey: 'alphabetically' },
{ dataView: 'last-publish', sortKey: 'updatedAt' },
+ { dataView: 'last-interacted', sortKey: 'lastInteractedWith' },
];
const DEFAULT_PER_PAGE = 50;
-const DEFAULT_STATUS_GROUP = 'all';
const DEFAULT_SITE_TYPE = 'non-p2';
const SitesDashboard = ( {
@@ -89,15 +88,17 @@ const SitesDashboard = ( {
perPage = DEFAULT_PER_PAGE,
search,
newSiteID,
- status = DEFAULT_STATUS_GROUP,
+ status,
siteType = DEFAULT_SITE_TYPE,
},
selectedSite,
initialSiteFeature = DOTCOM_OVERVIEW,
selectedSiteFeaturePreview = undefined,
+ sectionName,
}: SitesDashboardProps ) => {
const [ initialSortApplied, setInitialSortApplied ] = useState( false );
-
+ const isWide = useBreakpoint( WIDE_BREAKPOINT );
+ const isDesktop = useBreakpoint( DESKTOP_BREAKPOINT );
const { hasSitesSortingPreferenceLoaded, sitesSorting, onSitesSortingChange } = useSitesSorting();
const sitesFilterCallback = ( site: SiteExcerptData ) => {
const { options } = site || {};
@@ -136,6 +137,22 @@ const SitesDashboard = ( {
useShowSiteTransferredNotice();
const siteStatusGroups = useSiteStatusGroups();
+ const getSiteNameColWidth = ( isDesktop: boolean, isWide: boolean ) => {
+ if ( isWide ) {
+ return '40%';
+ }
+ if ( isDesktop ) {
+ return '50%';
+ }
+ return '70%';
+ };
+
+ // Limit fields on breakpoints smaller than 960px wide.
+ const desktopFields = [ 'site', 'plan', 'status', 'last-publish', 'stats', 'actions' ];
+ const mobileFields = [ 'site', 'actions' ];
+
+ const getFieldsByBreakpoint = ( isDesktop: boolean ) =>
+ isDesktop ? desktopFields : mobileFields;
// Create the DataViews state based on initial values
const defaultDataViewsState = {
@@ -143,24 +160,71 @@ const SitesDashboard = ( {
page,
perPage,
search: search ?? '',
- hiddenFields: [
- addDummyDataViewPrefix( 'site' ),
- addDummyDataViewPrefix( 'last-publish' ),
- addDummyDataViewPrefix( 'last-interacted' ),
- addDummyDataViewPrefix( 'status' ),
- ],
- filters: [
- {
- field: addDummyDataViewPrefix( 'status' ),
- operator: 'in',
- value: siteStatusGroups.find( ( item ) => item.slug === status )?.value || 1,
- },
- ],
+ fields: getFieldsByBreakpoint( isDesktop ),
+ ...( status
+ ? {
+ filters: [
+ {
+ field: 'status',
+ operator: 'is',
+ value: siteStatusGroups.find( ( item ) => item.slug === status )?.value || 1,
+ },
+ ],
+ }
+ : {} ),
selectedItem: selectedSite,
type: selectedSite ? DATAVIEWS_LIST : DATAVIEWS_TABLE,
+ layout: {
+ styles: {
+ site: {
+ width: getSiteNameColWidth( isDesktop, isWide ),
+ },
+ plan: {
+ width: '100px',
+ },
+ status: {
+ width: '116px',
+ },
+ 'last-publish': {
+ width: '120px',
+ },
+ stats: {
+ width: '80px',
+ },
+ actions: {
+ width: '48px',
+ },
+ },
+ },
} as DataViewsState;
const [ dataViewsState, setDataViewsState ] = useState< DataViewsState >( defaultDataViewsState );
+ useEffect( () => {
+ const fields = getFieldsByBreakpoint( isDesktop );
+ const fieldsForBreakpoint = [ ...fields ].sort().toString();
+ const existingFields = [ ...( dataViewsState?.fields ?? [] ) ].sort().toString();
+ // Compare the content of the arrays, not its referrences that will always be different.
+ // sort() sorts the array in place, so we need to clone them first.
+ if ( existingFields !== fieldsForBreakpoint ) {
+ setDataViewsState( ( prevState ) => ( { ...prevState, fields } ) );
+ }
+
+ const siteNameColumnWidth = getSiteNameColWidth( isDesktop, isWide );
+ if ( dataViewsState.layout.styles.site.width !== siteNameColumnWidth ) {
+ setDataViewsState( ( prevState ) => ( {
+ ...prevState,
+ layout: {
+ styles: {
+ ...prevState.layout.styles,
+ site: {
+ width: siteNameColumnWidth,
+ },
+ },
+ },
+ } ) );
+ }
+ }, [ isDesktop, isWide, dataViewsState?.fields, dataViewsState?.layout?.styles?.site?.width ] );
+
useSyncSelectedSite( dataViewsState, setDataViewsState, selectedSite );
const { selectedSiteFeature, setSelectedSiteFeature } = useSyncSelectedSiteFeature( {
@@ -201,25 +265,23 @@ const SitesDashboard = ( {
// Get the status group slug.
const statusSlug = useMemo( () => {
- const statusFilter = dataViewsState.filters.find(
- ( filter ) => filter.field === addDummyDataViewPrefix( 'status' )
- );
- const statusNumber = statusFilter?.value || 1;
- return ( siteStatusGroups.find( ( status ) => status.value === statusNumber )?.slug ||
- 'all' ) as GroupableSiteLaunchStatuses;
+ const statusFilter = dataViewsState.filters?.find( ( filter ) => filter.field === 'status' );
+ const statusNumber = statusFilter?.value;
+ return siteStatusGroups.find( ( status ) => status.value === statusNumber )
+ ?.slug as GroupableSiteLaunchStatuses;
}, [ dataViewsState.filters, siteStatusGroups ] );
// Filter sites list by status group.
const { currentStatusGroup } = useSitesListGrouping( allSites, {
- status: statusSlug,
+ status: statusSlug || 'all',
showHidden: true,
} );
// Perform sorting actions
const sortedSites = useSitesListSorting( currentStatusGroup, {
- sortKey: siteSortingKeys.find( ( key ) => key.dataView === dataViewsState.sort.field )
+ sortKey: siteSortingKeys.find( ( key ) => key.dataView === dataViewsState.sort?.field )
?.sortKey as SitesSortKey,
- sortOrder: dataViewsState.sort.direction || undefined,
+ sortOrder: dataViewsState.sort?.direction || undefined,
} );
// Filter sites list by search query.
@@ -227,10 +289,13 @@ const SitesDashboard = ( {
search: dataViewsState.search,
} );
- const paginatedSites = filteredSites.slice(
- ( dataViewsState.page - 1 ) * dataViewsState.perPage,
- dataViewsState.page * dataViewsState.perPage
- );
+ const paginatedSites =
+ dataViewsState.page && dataViewsState.perPage
+ ? filteredSites.slice(
+ ( dataViewsState.page - 1 ) * dataViewsState.perPage,
+ dataViewsState.page * dataViewsState.perPage
+ )
+ : filteredSites;
const onboardingTours = useOnboardingTours();
@@ -241,8 +306,8 @@ const SitesDashboard = ( {
useEffect( () => {
const queryParams = {
search: dataViewsState.search?.trim(),
- status: statusSlug === DEFAULT_STATUS_GROUP ? undefined : statusSlug,
- page: dataViewsState.page > 1 ? dataViewsState.page : undefined,
+ status: statusSlug,
+ page: dataViewsState.page && dataViewsState.page > 1 ? dataViewsState.page : undefined,
'per-page': dataViewsState.perPage === DEFAULT_PER_PAGE ? undefined : dataViewsState.perPage,
};
@@ -251,9 +316,9 @@ const SitesDashboard = ( {
// Update site sorting preference on change
useEffect( () => {
- if ( dataViewsState.sort.field ) {
+ if ( dataViewsState.sort?.field ) {
onSitesSortingChange( {
- sortKey: siteSortingKeys.find( ( key ) => key.dataView === dataViewsState.sort.field )
+ sortKey: siteSortingKeys.find( ( key ) => key.dataView === dataViewsState.sort?.field )
?.sortKey as SitesSortKey,
sortOrder: dataViewsState.sort.direction || 'asc',
} );
@@ -313,7 +378,7 @@ const SitesDashboard = ( {
{ ! isNarrowView && { dashboardTitle } }
-
+
@@ -379,6 +444,7 @@ const SitesDashboard = ( {
setSelectedSiteFeature={ setSelectedSiteFeature }
closeSitePreviewPane={ closeSitePreviewPane }
changeSitePreviewPane={ changeSitePreviewPane }
+ sectionName={ sectionName }
/>
diff --git a/client/hosting/sites/components/sites-dataviews/dataviews-fields/site-field.tsx b/client/hosting/sites/components/sites-dataviews/dataviews-fields/site-field.tsx
index 7deadde9cfe6a0..0b9c0c865929a1 100644
--- a/client/hosting/sites/components/sites-dataviews/dataviews-fields/site-field.tsx
+++ b/client/hosting/sites/components/sites-dataviews/dataviews-fields/site-field.tsx
@@ -35,7 +35,7 @@ type Props = {
const SiteListTile = styled( ListTile )`
gap: 0;
margin-inline-end: 0;
- width: 295px;
+ width: 280px;
.preview-hidden & {
gap: 12px;
diff --git a/client/hosting/sites/components/sites-dataviews/index.tsx b/client/hosting/sites/components/sites-dataviews/index.tsx
index 842e7793b8bfc8..2b742a679ebf25 100644
--- a/client/hosting/sites/components/sites-dataviews/index.tsx
+++ b/client/hosting/sites/components/sites-dataviews/index.tsx
@@ -1,5 +1,3 @@
-import { DESKTOP_BREAKPOINT, WIDE_BREAKPOINT } from '@automattic/viewport';
-import { useBreakpoint } from '@automattic/viewport-react';
import { useI18n } from '@wordpress/react-i18n';
import { useCallback, useEffect, useMemo, useState } from 'react';
import ItemsDataViews from 'calypso/a8c-for-agencies/components/items-dashboard/items-dataviews';
@@ -10,14 +8,11 @@ import { useSelector } from 'calypso/state';
import { getCurrentUserId } from 'calypso/state/current-user/selectors';
import ActionsField from './dataviews-fields/actions-field';
import SiteField from './dataviews-fields/site-field';
-import { SiteInfo } from './interfaces';
-import { SiteSort } from './sites-site-sort';
import { SiteStats } from './sites-site-stats';
import { SiteStatus } from './sites-site-status';
-import { addDummyDataViewPrefix } from './utils';
import type { SiteExcerptData } from '@automattic/sites';
+import type { Field } from '@wordpress/dataviews';
import type {
- DataViewsColumn,
DataViewsPaginationInfo,
DataViewsState,
ItemsDataViewsType,
@@ -58,17 +53,6 @@ const DotcomSitesDataViews = ( {
}: Props ) => {
const { __ } = useI18n();
const userId = useSelector( getCurrentUserId );
- const isWide = useBreakpoint( WIDE_BREAKPOINT );
- const isDesktop = useBreakpoint( DESKTOP_BREAKPOINT );
- const getSiteNameColWidth = ( isDesktop: boolean, isWide: boolean ) => {
- if ( isWide ) {
- return '40%';
- }
- if ( isDesktop ) {
- return '50%';
- }
- return '70%';
- };
const openSitePreviewPane = useCallback(
( site: SiteExcerptData ) => {
@@ -116,128 +100,80 @@ const DotcomSitesDataViews = ( {
const siteStatusGroups = useSiteStatusGroups();
// Generate DataViews table field-columns
- const fields = useMemo< DataViewsColumn[] >(
+ const fields = useMemo< Field< SiteExcerptData >[] >(
() => [
{
id: 'site',
- header: (
-
- { __( 'Site' ) }
-
- ),
- width: getSiteNameColWidth( isDesktop, isWide ),
- getValue: ( { item }: { item: SiteInfo } ) => item.URL,
- render: ( { item }: { item: SiteInfo } ) => {
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: { __( 'Site' ) } ,
+ getValue: ( { item }: { item: SiteExcerptData } ) => item.URL,
+ render: ( { item }: { item: SiteExcerptData } ) => {
return ;
},
enableHiding: false,
- enableSorting: false,
+ enableSorting: true,
},
{
id: 'plan',
- header: { __( 'Plan' ) } ,
- render: ( { item }: { item: SiteInfo } ) => ,
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: { __( 'Plan' ) } ,
+ render: ( { item }: { item: SiteExcerptData } ) => (
+
+ ),
enableHiding: false,
enableSorting: false,
- width: '100px',
},
{
id: 'status',
- header: { __( 'Status' ) } ,
- render: ( { item }: { item: SiteInfo } ) => ,
+ label: __( 'Status' ),
+ render: ( { item }: { item: SiteExcerptData } ) => ,
enableHiding: false,
enableSorting: false,
- width: '116px',
+ elements: siteStatusGroups,
+ filterBy: {
+ operators: [ 'is' ],
+ },
},
{
id: 'last-publish',
- header: (
-
- { __( 'Last Published' ) }
-
- ),
- render: ( { item }: { item: SiteInfo } ) =>
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: { __( 'Last Published' ) } ,
+ render: ( { item }: { item: SiteExcerptData } ) =>
item.options?.updated_at ? : '',
enableHiding: false,
- enableSorting: false,
- width: '120px',
+ enableSorting: true,
},
{
id: 'stats',
- header: (
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: (
<>
{ __( 'Stats' ) }
>
),
- render: ( { item }: { item: SiteInfo } ) => ,
+ render: ( { item }: { item: SiteExcerptData } ) => ,
enableHiding: false,
enableSorting: false,
- width: '80px',
},
{
id: 'actions',
- header: { __( 'Actions' ) } ,
- render: ( { item }: { item: SiteInfo } ) => ,
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: { __( 'Actions' ) } ,
+ render: ( { item }: { item: SiteExcerptData } ) => ,
enableHiding: false,
enableSorting: false,
- width: '48px',
- },
- // Dummy fields to allow people to sort by them on mobile.
- {
- id: addDummyDataViewPrefix( 'site' ),
- header: { __( 'Site' ) } ,
- render: () => null,
- enableHiding: false,
- enableSorting: true,
- },
- {
- id: addDummyDataViewPrefix( 'last-publish' ),
- header: { __( 'Last Published' ) } ,
- render: () => null,
- enableHiding: false,
- enableSorting: true,
},
{
- id: addDummyDataViewPrefix( 'last-interacted' ),
- header: __( 'Last Interacted' ),
+ id: 'last-interacted',
+ label: __( 'Last Interacted' ),
render: () => null,
enableHiding: false,
enableSorting: true,
- },
- {
- id: addDummyDataViewPrefix( 'status' ),
- header: __( 'Status' ),
- render: () => null,
- type: 'enumeration',
- elements: siteStatusGroups,
- filterBy: {
- operators: [ 'in' ],
- },
- enableHiding: false,
- enableSorting: false,
+ getValue: () => null,
},
],
- [
- __,
- openSitePreviewPane,
- userId,
- dataViewsState,
- setDataViewsState,
- isWide,
- isDesktop,
- siteStatusGroups,
- ]
+ [ __, openSitePreviewPane, userId, dataViewsState, setDataViewsState, siteStatusGroups ]
);
// Create the itemData packet state
@@ -250,6 +186,7 @@ const DotcomSitesDataViews = ( {
setDataViewsState: setDataViewsState,
dataViewsState: dataViewsState,
pagination: paginationInfo,
+ defaultLayouts: { table: {} },
} );
// Update the itemData packet
diff --git a/client/hosting/sites/components/sites-dataviews/interfaces.ts b/client/hosting/sites/components/sites-dataviews/interfaces.ts
deleted file mode 100644
index 12cbc0154eeb4e..00000000000000
--- a/client/hosting/sites/components/sites-dataviews/interfaces.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import type { SiteExcerptData } from '@automattic/sites';
-
-export interface SiteInfo extends SiteExcerptData {
- id: number;
-}
diff --git a/client/hosting/sites/components/sites-dataviews/sites-site-sort.tsx b/client/hosting/sites/components/sites-dataviews/sites-site-sort.tsx
deleted file mode 100644
index ce2cf8e453e8b3..00000000000000
--- a/client/hosting/sites/components/sites-dataviews/sites-site-sort.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copied from client/a8c-for-agencies/sections/sites/site-sort/index.tsx as we don't have SitesDashboardContext here.
-import { Icon } from '@wordpress/icons';
-import clsx from 'clsx';
-import {
- defaultSortIcon,
- ascendingSortIcon,
- descendingSortIcon,
-} from 'calypso/jetpack-cloud/sections/agency-dashboard/icons';
-import { addDummyDataViewPrefix, removeDummyDataViewPrefix } from './utils';
-import type { DataViewsState } from 'calypso/a8c-for-agencies/components/items-dashboard/items-dataviews/interfaces';
-
-import 'calypso/a8c-for-agencies/sections/sites/site-sort/style.scss';
-
-const SORT_DIRECTION_ASC = 'asc';
-const SORT_DIRECTION_DESC = 'desc';
-
-interface SiteSortProps {
- columnKey: string;
- isLargeScreen?: boolean;
- children?: React.ReactNode;
- isSortable?: boolean;
- dataViewsState: DataViewsState;
- setDataViewsState: ( callback: ( prevState: DataViewsState ) => DataViewsState ) => void;
-}
-
-export const SiteSort = ( {
- columnKey,
- isLargeScreen,
- children,
- isSortable,
- dataViewsState,
- setDataViewsState,
-}: SiteSortProps ) => {
- const { field, direction } = dataViewsState.sort;
-
- const isDefault = removeDummyDataViewPrefix( field ) !== columnKey || ! field || ! direction;
-
- const setSort = () => {
- const updatedSort = { ...dataViewsState.sort };
- if ( isDefault ) {
- updatedSort.field = addDummyDataViewPrefix( columnKey );
- updatedSort.direction = SORT_DIRECTION_ASC;
- } else if ( direction === SORT_DIRECTION_ASC ) {
- updatedSort.direction = SORT_DIRECTION_DESC;
- } else if ( direction === SORT_DIRECTION_DESC ) {
- updatedSort.field = addDummyDataViewPrefix( 'last-interacted' );
- updatedSort.direction = SORT_DIRECTION_DESC;
- }
-
- setDataViewsState( ( sitesViewState ) => ( {
- ...sitesViewState,
- sort: updatedSort,
- } ) );
- };
-
- const getSortIcon = () => {
- if ( isDefault ) {
- return defaultSortIcon;
- } else if ( direction === SORT_DIRECTION_ASC ) {
- return ascendingSortIcon;
- } else if ( direction === SORT_DIRECTION_DESC ) {
- return descendingSortIcon;
- }
- return defaultSortIcon;
- };
-
- if ( ! isSortable ) {
- return { children } ;
- }
-
- const handleOnKeyDown = ( event: React.KeyboardEvent< HTMLDivElement > ) => {
- if ( event.key === 'Enter' || event.key === ' ' ) {
- setSort();
- }
- };
-
- return (
-
- { children }
- { isSortable && (
-
- ) }
-
- );
-};
diff --git a/client/hosting/sites/components/sites-dataviews/style.scss b/client/hosting/sites/components/sites-dataviews/style.scss
index c6fc14864ea5db..0e7fd27a3090af 100644
--- a/client/hosting/sites/components/sites-dataviews/style.scss
+++ b/client/hosting/sites/components/sites-dataviews/style.scss
@@ -15,6 +15,7 @@
flex-direction: row;
padding-bottom: 8px;
padding-top: 8px;
+ overflow: hidden;
.button {
margin: 0;
@@ -40,8 +41,10 @@
white-space: nowrap;
}
-.sites-dataviews__site-url,
-.sites-dataviews__site-wp-admin-url {
+.dataviews-view-list .sites-dataviews__site-url,
+.dataviews-view-list .sites-dataviews__site-wp-admin-url,
+.dataviews-view-table .sites-dataviews__site-url,
+.dataviews-view-table .sites-dataviews__site-wp-admin-url {
color: var(--color-neutral-70);
font-size: rem(14px);
font-weight: 400;
diff --git a/client/hosting/sites/components/sites-dataviews/utils.ts b/client/hosting/sites/components/sites-dataviews/utils.ts
index 5aa278bc751e11..c652af6ed131c3 100644
--- a/client/hosting/sites/components/sites-dataviews/utils.ts
+++ b/client/hosting/sites/components/sites-dataviews/utils.ts
@@ -21,10 +21,6 @@ export function getSitesPagination(
return { totalItems, totalPages };
}
-export function addDummyDataViewPrefix( dataView: string ) {
- return `${ DUMMY_DATA_VIEW_PREFIX }${ dataView }`;
-}
-
export function removeDummyDataViewPrefix( dataView: string ) {
return dataView.replace( DUMMY_DATA_VIEW_PREFIX, '' );
}
diff --git a/client/hosting/sites/components/style.scss b/client/hosting/sites/components/style.scss
index f657e3ca0e4427..5ab14d3d7cb097 100644
--- a/client/hosting/sites/components/style.scss
+++ b/client/hosting/sites/components/style.scss
@@ -53,11 +53,6 @@
padding-inline-end: 16px;
text-align: end;
}
-
- .dataviews-view-table-wrapper {
- overflow: auto;
- }
-
@media (min-width: $break-large) {
background: inherit;
@@ -170,13 +165,6 @@
.components-button.is-compact.has-icon:not(.has-text).dataviews-filters-button {
min-width: 40px;
}
- .components-button.is-compact.is-tertiary:not(.dataviews-filters-button) {
- display: none;
- }
-
- .components-button.is-compact.is-tertiary {
- margin-right: 8px;
- }
.dataviews-filters__view-actions {
padding-bottom: 8px;
@@ -204,10 +192,6 @@
display: none;
}
- .dataviews-view-table-wrapper td:nth-child(n+2):nth-child(-n+5) {
- display: none;
- }
-
tr td:first-child {
padding-inline-start: 8px;
}
@@ -225,24 +209,6 @@
overflow: hidden;
}
}
-
- .dataviews-view-table-wrapper {
- flex: 1;
- overflow-y: auto;
-
- table {
- width: 100%;
- max-width: 100vw;
- }
-
- th[data-field-id="plan"],
- th[data-field-id="status"],
- th[data-field-id="last-publish"],
- th[data-field-id="stats"] {
- display: none;
- }
-
- }
}
@media (max-width: $break-wide) {
@@ -343,11 +309,6 @@
overflow-y: auto;
max-height: calc(100vh - 300px); /* 300px is the size of all content above the dataview in list style, which includes our CTA elements, the pagination bottom bar, and an additional 20px margin. */
- li {
- padding: 8px 24px;
- border-bottom: 1px solid var(--color-accent-5);
- }
-
.dataviews-view-list__fields {
display: flex;
justify-content: space-between;
@@ -508,7 +469,7 @@
display: flex;
flex: 1;
height: 100%;
- overflow: hidden;
+ overflow: auto;
margin-top: 24px;
}
}
diff --git a/client/hosting/sites/controller.tsx b/client/hosting/sites/controller.tsx
index 9b91e9b4b5db4c..f77ae15c462e5f 100644
--- a/client/hosting/sites/controller.tsx
+++ b/client/hosting/sites/controller.tsx
@@ -1,7 +1,4 @@
-import {
- DEFAULT_SITE_LAUNCH_STATUS_GROUP_VALUE,
- siteLaunchStatusGroupValues,
-} from '@automattic/sites';
+import { siteLaunchStatusGroupValues } from '@automattic/sites';
import { Global, css } from '@emotion/react';
import { removeQueryArgs } from '@wordpress/url';
import AsyncLoad from 'calypso/components/async-load';
@@ -35,7 +32,6 @@ export function sanitizeQueryParameters( context: PageJSContext, next: () => voi
* in the route.
*/
if ( context.query.status === undefined ) {
- context.query.status = DEFAULT_SITE_LAUNCH_STATUS_GROUP_VALUE;
return next();
}
diff --git a/client/hosting/sites/hooks/use-sites-dashboard-import-site-url.ts b/client/hosting/sites/hooks/use-sites-dashboard-import-site-url.ts
index 18661b0adc8bf2..187b1b44d010fb 100644
--- a/client/hosting/sites/hooks/use-sites-dashboard-import-site-url.ts
+++ b/client/hosting/sites/hooks/use-sites-dashboard-import-site-url.ts
@@ -7,7 +7,7 @@ export const useSitesDashboardImportSiteUrl = (
additionalParameters: Record< string, Primitive >
) => {
const [ isLoadingExperiment, experimentAssignment ] = useExperiment(
- 'calypso_optimized_migration_flow'
+ 'calypso_optimized_migration_flow_v2'
);
if ( isLoadingExperiment ) {
diff --git a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/site-sort/index.tsx b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/site-sort/index.tsx
index f1e93fa002c206..3583d1007ec6be 100644
--- a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/site-sort/index.tsx
+++ b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/site-sort/index.tsx
@@ -31,20 +31,24 @@ export default function SiteSort( {
const { sort } = useContext( SitesOverviewContext );
const dispatch = useDispatch();
- const { field, direction } = sort;
+ const { field, direction } = sort ?? {};
const isDefault = field !== SITE_COLUMN_KEY_MAP?.[ columnKey ] || ! field || ! direction;
const setSort = () => {
- const updatedSort = { ...sort };
+ let updatedSort = sort;
if ( isDefault ) {
- updatedSort.field = SITE_COLUMN_KEY_MAP?.[ columnKey ];
- updatedSort.direction = SORT_DIRECTION_ASC;
+ updatedSort = {
+ field: SITE_COLUMN_KEY_MAP?.[ columnKey ],
+ direction: SORT_DIRECTION_ASC,
+ };
} else if ( direction === SORT_DIRECTION_ASC ) {
- updatedSort.direction = SORT_DIRECTION_DESC;
+ updatedSort = {
+ field: SITE_COLUMN_KEY_MAP?.[ columnKey ],
+ direction: SORT_DIRECTION_DESC,
+ };
} else if ( direction === SORT_DIRECTION_DESC ) {
- updatedSort.field = '';
- updatedSort.direction = '';
+ updatedSort = undefined;
}
dispatch( updateSort( updatedSort ) );
diff --git a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dashboard-v2.tsx b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dashboard-v2.tsx
index ca87315ee85c7b..f555374a9d6486 100644
--- a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dashboard-v2.tsx
+++ b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dashboard-v2.tsx
@@ -90,19 +90,28 @@ export default function SitesDashboardV2() {
filter?.issueTypes?.map( ( issueType ) => {
return {
field: 'status',
- operator: 'in',
+ operator: 'is',
value: filtersMap.find( ( filterMap ) => filterMap.filterType === issueType )?.ref || 1,
};
} ) || [],
- hiddenFields: [ 'status' ],
- layout: {},
+ fields: [
+ 'site',
+ 'stats',
+ 'boost',
+ 'backup',
+ 'monitor',
+ 'scan',
+ 'plugins',
+ 'favorite',
+ 'actions',
+ ],
selectedSite: undefined,
} );
const { data, isError, isLoading, refetch } = useFetchDashboardSites( {
isPartnerOAuthTokenLoaded,
searchQuery: search,
- currentPage: sitesViewState.page,
+ currentPage: sitesViewState.page ?? 1,
filter,
sort,
perPage: sitesViewState.perPage,
@@ -148,7 +157,7 @@ export default function SitesDashboardV2() {
if ( path === '/sites?issue_types=all_issues' ) {
setSitesViewState( {
...sitesViewState,
- filters: [ { field: 'status', operator: 'in', value: 1 } ],
+ filters: [ { field: 'status', operator: 'is', value: 1 } ],
search: '',
page: 1,
} );
diff --git a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/index.tsx b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/index.tsx
index 183d5786c782b2..43f3808d91d597 100644
--- a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/index.tsx
+++ b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/index.tsx
@@ -13,8 +13,9 @@ import { JETPACK_MANAGE_ONBOARDING_TOURS_EXAMPLE_SITE } from 'calypso/jetpack-cl
import TextPlaceholder from 'calypso/jetpack-cloud/sections/partner-portal/text-placeholder';
import SiteSetFavorite from '../site-set-favorite';
import SiteSort from '../site-sort';
-import { AllowedTypes, Site } from '../types';
-import { SitesDataViewsProps, SiteInfo } from './interfaces';
+import { AllowedTypes, Site, SiteData } from '../types';
+import { SitesDataViewsProps } from './interfaces';
+import type { Field } from '@wordpress/dataviews';
import './style.scss';
@@ -30,7 +31,8 @@ const SitesDataViews = ( {
const translate = useTranslate();
const { showOnlyFavorites } = useContext( SitesDashboardContext );
const totalSites = showOnlyFavorites ? data?.totalFavorites || 0 : data?.total || 0;
- const sitesPerPage = sitesViewState.perPage > 0 ? sitesViewState.perPage : 20;
+ const sitesPerPage =
+ sitesViewState.perPage && sitesViewState.perPage > 0 ? sitesViewState.perPage : 20;
const totalPages = Math.ceil( totalSites / sitesPerPage );
const sites = useFormattedSites( data?.sites ?? [] );
@@ -46,7 +48,7 @@ const SitesDataViews = ( {
);
const renderField = useCallback(
- ( column: AllowedTypes, item: SiteInfo ) => {
+ ( column: AllowedTypes, item: SiteData ) => {
if ( isLoading ) {
return ;
}
@@ -67,15 +69,14 @@ const SitesDataViews = ( {
);
// todo - refactor: extract fields, along actions, to the upper component
- const fields = useMemo(
+ const fields = useMemo< Field< SiteData >[] >(
() => [
{
id: 'status',
- header: translate( 'Status' ),
- getValue: ( { item }: { item: SiteInfo } ) =>
+ label: translate( 'Status' ),
+ getValue: ( { item }: { item: SiteData } ) =>
item.site.error || item.scan.status === 'critical',
- render: () => {},
- type: 'enumeration',
+ render: () => null,
elements: [
{ value: 1, label: translate( 'Needs Attention' ) },
{ value: 2, label: translate( 'Backup Failed' ) },
@@ -86,14 +87,15 @@ const SitesDataViews = ( {
{ value: 7, label: translate( 'Plugins Needing Updates' ) },
],
filterBy: {
- operators: [ 'in' ],
+ operators: [ 'is' ],
},
enableHiding: false,
enableSorting: false,
},
{
id: 'site',
- header: (
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: (
<>
@@ -102,8 +104,8 @@ const SitesDataViews = ( {
>
),
- getValue: ( { item }: { item: SiteInfo } ) => item.site.value.url,
- render: ( { item }: { item: SiteInfo } ) => {
+ getValue: ( { item }: { item: SiteData } ) => item.site.value.url,
+ render: ( { item }: { item: SiteData } ) => {
if ( isLoading ) {
return ;
}
@@ -121,63 +123,70 @@ const SitesDataViews = ( {
},
{
id: 'stats',
- header: STATS ,
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: STATS ,
getValue: () => '-',
- render: ( { item }: { item: SiteInfo } ) => renderField( 'stats', item ),
+ render: ( { item }: { item: SiteData } ) => renderField( 'stats', item ),
enableHiding: false,
enableSorting: false,
},
{
id: 'boost',
- header: BOOST ,
- getValue: ( { item }: { item: SiteInfo } ) => item.boost.status,
- render: ( { item }: { item: SiteInfo } ) => renderField( 'boost', item ),
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: BOOST ,
+ getValue: ( { item }: { item: SiteData } ) => item.boost.status,
+ render: ( { item }: { item: SiteData } ) => renderField( 'boost', item ),
enableHiding: false,
enableSorting: false,
},
{
id: 'backup',
- header: BACKUP ,
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: BACKUP ,
getValue: () => '-',
- render: ( { item }: { item: SiteInfo } ) => renderField( 'backup', item ),
+ render: ( { item }: { item: SiteData } ) => renderField( 'backup', item ),
enableHiding: false,
enableSorting: false,
},
{
id: 'monitor',
- header: MONITOR ,
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: MONITOR ,
getValue: () => '-',
- render: ( { item }: { item: SiteInfo } ) => renderField( 'monitor', item ),
+ render: ( { item }: { item: SiteData } ) => renderField( 'monitor', item ),
enableHiding: false,
enableSorting: false,
},
{
id: 'scan',
- header: SCAN ,
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: SCAN ,
getValue: () => '-',
- render: ( { item }: { item: SiteInfo } ) => renderField( 'scan', item ),
+ render: ( { item }: { item: SiteData } ) => renderField( 'scan', item ),
enableHiding: false,
enableSorting: false,
},
{
id: 'plugins',
- header: PLUGINS ,
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: PLUGINS ,
getValue: () => '-',
- render: ( { item }: { item: SiteInfo } ) => renderField( 'plugin', item ),
+ render: ( { item }: { item: SiteData } ) => renderField( 'plugin', item ),
enableHiding: false,
enableSorting: false,
},
{
id: 'favorite',
- header: (
+ // @ts-expect-error -- Need to fix the label type upstream in @wordpress/dataviews to support React elements.
+ label: (
),
- getValue: ( { item }: { item: SiteInfo } ) => item.isFavorite,
- render: ( { item }: { item: SiteInfo } ) => {
+ getValue: ( { item }: { item: SiteData } ) => item.isFavorite,
+ render: ( { item }: { item: SiteData } ) => {
if ( isLoading ) {
return ;
}
@@ -196,8 +205,8 @@ const SitesDataViews = ( {
},
{
id: 'actions',
- getValue: ( { item }: { item: SiteInfo } ) => item.isFavorite,
- render: ( { item }: { item: SiteInfo } ) => {
+ getValue: ( { item }: { item: SiteData } ) => item.isFavorite,
+ render: ( { item }: { item: SiteData } ) => {
if ( isLoading ) {
return ;
}
@@ -235,44 +244,44 @@ const SitesDataViews = ( {
id: 'pause-monitor',
label: translate( 'Pause Monitor' ),
supportsBulk: true,
- isEligible( site: SiteInfo ) {
+ isEligible( site: SiteData ) {
return site.monitor.status === 'active';
},
callback() {
- // todo: pause monitor. Param: sites: SiteInfo[]
+ // todo: pause monitor. Param: sites: SiteData[]
},
},
{
id: 'resume-monitor',
label: translate( 'Resume Monitor' ),
supportsBulk: true,
- isEligible( site: SiteInfo ) {
+ isEligible( site: SiteData ) {
return site.monitor.status === 'inactive';
},
callback() {
- // todo: resume monitor. Param: sites: SiteInfo[]
+ // todo: resume monitor. Param: sites: SiteData[]
},
},
{
id: 'custom-notification',
label: translate( 'Custom Notification' ),
supportsBulk: true,
- isEligible( site: SiteInfo ) {
+ isEligible( site: SiteData ) {
return site.monitor.status === 'active';
},
callback() {
- // todo: custom notification. Param: sites: SiteInfo[]
+ // todo: custom notification. Param: sites: SiteData[]
},
},
{
id: 'reset-notification',
label: translate( 'Reset Notification' ),
supportsBulk: true,
- isEligible( site: SiteInfo ) {
+ isEligible( site: SiteData ) {
return site.monitor.status === 'active';
},
callback() {
- // todo: reset notification. Param: sites: SiteInfo[]
+ // todo: reset notification. Param: sites: SiteData[]
},
},
],
@@ -317,12 +326,11 @@ const SitesDataViews = ( {
view={ sitesViewState }
search
searchLabel={ translate( 'Search for sites' ) }
- getItemId={ ( item: SiteInfo ) => {
- item.id = item.site.value.blog_id; // setting the id because of a issue with the DataViews component
- return item.id;
+ getItemId={ ( item: SiteData ) => {
+ return item.site.value.blog_id.toString();
} }
onChangeView={ onSitesViewChange }
- supportedLayouts={ [ 'table' ] }
+ defaultLayouts={ { table: {} } }
actions={ [] } // Replace with actions when bulk selections are implemented.
isLoading={ isLoading }
/>
diff --git a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/interfaces.ts b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/interfaces.ts
index c10e1bfb8e77c8..190e45ab8e83bf 100644
--- a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/interfaces.ts
+++ b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/interfaces.ts
@@ -1,4 +1,5 @@
-import { type DashboardSortInterface, Site, SiteData } from '../types';
+import { type Site } from '../types';
+import type { View } from '@wordpress/dataviews';
export interface SitesDataResponse {
sites: Array< Site >;
@@ -18,24 +19,6 @@ export interface SitesDataViewsProps {
sitesViewState: SitesViewState;
}
-export interface Filter {
- field: string;
- operator: string;
- value: number;
-}
-
-export interface SitesViewState {
- type: 'table' | 'list' | 'grid';
- perPage: number;
- page: number;
- sort: DashboardSortInterface;
- search: string;
- filters: Filter[];
- hiddenFields: string[];
- layout: object;
+export type SitesViewState = View & {
selectedSite?: Site | undefined;
-}
-
-export interface SiteInfo extends SiteData {
- id: number;
-}
+};
diff --git a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/style.scss b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/style.scss
index 4b0b202a9a5a5b..88d357e374de56 100644
--- a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/style.scss
+++ b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/style.scss
@@ -249,10 +249,6 @@
.dataviews-loading p {
display: none;
}
-
- .dataviews-view-table-wrapper {
- height: 0 !important;
- }
}
.dataviews-wrapper:has(.dataviews-no-results) {
diff --git a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/types.d.ts b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/types.d.ts
deleted file mode 100644
index 9d670dfed75ded..00000000000000
--- a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/sites-dataviews/types.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-declare module '@wordpress/dataviews';
diff --git a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/style-dashboard-v2.scss b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/style-dashboard-v2.scss
index ca703e7d1f720e..5c85fa9a4d87e9 100644
--- a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/style-dashboard-v2.scss
+++ b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/style-dashboard-v2.scss
@@ -146,23 +146,6 @@
tr td:last-child {
padding-right: 8px;
}
-
- .dataviews-view-table-wrapper {
- table {
- width: 100%;
- max-width: 100vw;
- }
-
- th[data-field-id="stats"],
- th[data-field-id="boost"],
- th[data-field-id="backup"],
- th[data-field-id="monitor"],
- th[data-field-id="scan"],
- th[data-field-id="plugins"],
- th[data-field-id="favorite"] {
- display: none;
- }
- }
}
@media (max-width: $break-wide) {
diff --git a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/types.ts b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/types.ts
index b87fd3129fadce..739998c8bd70b5 100644
--- a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/types.ts
+++ b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/types.ts
@@ -1,5 +1,6 @@
import { TranslateResult } from 'i18n-calypso';
import { APIProductFamilyProduct } from 'calypso/state/partner-portal/types';
+import type { SortDirection } from '@wordpress/dataviews';
// All types based on which the data is populated on the agency dashboard table rows
export type AllowedTypes =
@@ -234,7 +235,7 @@ export type ActionEventNames = {
export interface DashboardSortInterface {
field: string;
- direction: 'asc' | 'desc' | '';
+ direction: SortDirection;
}
export interface DashboardOverviewContextInterface {
path: string;
@@ -245,7 +246,7 @@ export interface DashboardOverviewContextInterface {
showOnlyFavorites: boolean;
showOnlyDevelopmentSites: boolean;
};
- sort: DashboardSortInterface;
+ sort?: DashboardSortInterface;
showSitesDashboardV2: boolean;
}
diff --git a/client/jetpack-cloud/sections/pricing/style.scss b/client/jetpack-cloud/sections/pricing/style.scss
index 6bebdcb755db4b..315d731555e09b 100644
--- a/client/jetpack-cloud/sections/pricing/style.scss
+++ b/client/jetpack-cloud/sections/pricing/style.scss
@@ -5,18 +5,6 @@
@include jetpack-cloud-sections-shared-styles();
- // On cloud.jetpack.com, we want to override the primary color, used on
- // the featured product card, with --studio-pink-50.
- .jetpack-product-card.is-featured {
- .jetpack-product-card__header {
- background-color: var(--studio-pink-50);
- }
-
- .jetpack-product-card__body {
- border: solid 2px var(--studio-pink-50);
- }
- }
-
.layout__content {
overflow: clip;
}
diff --git a/client/jetpack-connect/colors.scss b/client/jetpack-connect/colors.scss
index 01c1e788b1929f..f2d6174ed96228 100644
--- a/client/jetpack-connect/colors.scss
+++ b/client/jetpack-connect/colors.scss
@@ -20,9 +20,9 @@
@mixin woocommerce-colors() {
// Override accent variables with WooCommerce colors
- --color-accent: var(--studio-pink-50);
- --color-accent-light: var(--studio-pink-30);
- --color-accent-dark: var(--studio-pink-70);
+ --color-accent: var(--studio-woocommerce-purple);
+ --color-accent-light: var(--studio-woocommerce-purple-30);
+ --color-accent-dark: var(--studio-woocommerce-purple-70);
--color-woocommerce-onboarding-background: #f6f7f7;
--color-woocommerce-header-border: #dcdcde;
diff --git a/client/jetpack-connect/style.scss b/client/jetpack-connect/style.scss
index 06e8b8b260cb75..ac0320a8fcdcf5 100644
--- a/client/jetpack-connect/style.scss
+++ b/client/jetpack-connect/style.scss
@@ -973,8 +973,8 @@ $colophon-height: 50px; // wpcomColophon element at the bottom
.auth-form__social,
.login__form-social {
.social-buttons__button {
- border-color: var(--studio-pink-50);
- color: var(--studio-pink-50);
+ border-color: var(--studio-woocommerce-purple);
+ color: var(--studio-woocommerce-purple);
&.disabled {
border-color: var(--studio-gray-30);
diff --git a/client/landing/stepper/constants.ts b/client/landing/stepper/constants.ts
index 28af88d659fea3..46345e3ba310ab 100644
--- a/client/landing/stepper/constants.ts
+++ b/client/landing/stepper/constants.ts
@@ -26,10 +26,12 @@ export const STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT = 'calypso_signup_step_nav_ne
export const STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO = 'calypso_signup_step_nav_go_to';
export const STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW = 'calypso_signup_step_nav_exit_flow';
-export const STEPPER_TRACKS_EVENTS = < const >[
+export const STEPPER_TRACKS_EVENTS_STEP_NAV = < const >[
STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT,
STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO,
STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW,
];
+
+export const STEPPER_TRACKS_EVENTS = < const >[ ...STEPPER_TRACKS_EVENTS_STEP_NAV ];
diff --git a/client/landing/stepper/declarative-flow/internals/analytics/record-submit-step.ts b/client/landing/stepper/declarative-flow/internals/analytics/record-step-navigation.ts
similarity index 74%
rename from client/landing/stepper/declarative-flow/internals/analytics/record-submit-step.ts
rename to client/landing/stepper/declarative-flow/internals/analytics/record-step-navigation.ts
index 0e66f259b7706d..e362b07dd8ad72 100644
--- a/client/landing/stepper/declarative-flow/internals/analytics/record-submit-step.ts
+++ b/client/landing/stepper/declarative-flow/internals/analytics/record-step-navigation.ts
@@ -1,17 +1,29 @@
import { resolveDeviceTypeByViewPort } from '@automattic/viewport';
import { reduce, snakeCase } from 'lodash';
+import { STEPPER_TRACKS_EVENTS_STEP_NAV } from 'calypso/landing/stepper/constants';
import { getStepOldSlug } from 'calypso/landing/stepper/declarative-flow/helpers/get-step-old-slug';
import { recordTracksEvent } from 'calypso/lib/analytics/tracks';
import { ProvidedDependencies } from '../types';
-export function recordSubmitStep(
- providedDependencies: ProvidedDependencies = {},
- intent: string,
- flow: string,
- step: string,
- variant?: string,
- additionalProps: ProvidedDependencies = {}
-) {
+export interface RecordStepNavigationParams {
+ event: ( typeof STEPPER_TRACKS_EVENTS_STEP_NAV )[ number ];
+ intent: string;
+ flow: string;
+ step: string;
+ variant?: string;
+ providedDependencies?: ProvidedDependencies;
+ additionalProps?: ProvidedDependencies;
+}
+
+export function recordStepNavigation( {
+ event,
+ intent,
+ flow,
+ step,
+ variant,
+ providedDependencies = {},
+ additionalProps = {},
+}: RecordStepNavigationParams ) {
const device = resolveDeviceTypeByViewPort();
const inputs = reduce(
providedDependencies,
@@ -58,7 +70,7 @@ export function recordSubmitStep(
{}
);
- recordTracksEvent( 'calypso_signup_actions_submit_step', {
+ recordTracksEvent( event, {
device,
flow,
variant,
@@ -70,7 +82,7 @@ export function recordSubmitStep(
const stepOldSlug = getStepOldSlug( step );
if ( stepOldSlug ) {
- recordTracksEvent( 'calypso_signup_actions_submit_step', {
+ recordTracksEvent( event, {
device,
flow,
variant,
diff --git a/client/landing/stepper/declarative-flow/internals/components/help-center/async.tsx b/client/landing/stepper/declarative-flow/internals/components/help-center/async.tsx
index 154e795349320f..f485bdab20aa7b 100644
--- a/client/landing/stepper/declarative-flow/internals/components/help-center/async.tsx
+++ b/client/landing/stepper/declarative-flow/internals/components/help-center/async.tsx
@@ -1,21 +1,16 @@
-import { HelpCenter, HelpCenterSelect } from '@automattic/data-stores';
-import { useDispatch, useSelect } from '@wordpress/data';
+import { HelpCenter } from '@automattic/data-stores';
+import { useDispatch } from '@wordpress/data';
+import { useCallback } from 'react';
import AsyncLoad from 'calypso/components/async-load';
const HELP_CENTER_STORE = HelpCenter.register();
const AsyncHelpCenter = () => {
const { setShowHelpCenter } = useDispatch( HELP_CENTER_STORE );
- const isShowingHelpCenter = useSelect(
- ( select ) => ( select( HELP_CENTER_STORE ) as HelpCenterSelect ).isHelpCenterShown(),
- []
- );
-
- const handleClose = () => setShowHelpCenter( false );
- if ( ! isShowingHelpCenter ) {
- return null;
- }
+ const handleClose = useCallback( () => {
+ setShowHelpCenter( false );
+ }, [ setShowHelpCenter ] );
return (
diff --git a/client/landing/stepper/declarative-flow/internals/hooks/use-step-navigation-with-tracking/index.ts b/client/landing/stepper/declarative-flow/internals/hooks/use-step-navigation-with-tracking/index.ts
index ace86d4efa62ae..1b2cc8d23ed1ea 100644
--- a/client/landing/stepper/declarative-flow/internals/hooks/use-step-navigation-with-tracking/index.ts
+++ b/client/landing/stepper/declarative-flow/internals/hooks/use-step-navigation-with-tracking/index.ts
@@ -1,9 +1,18 @@
import { OnboardSelect } from '@automattic/data-stores';
import { useSelect } from '@wordpress/data';
-import { useMemo } from '@wordpress/element';
-import { STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT } from 'calypso/landing/stepper/constants';
+import { useCallback, useMemo } from '@wordpress/element';
+import {
+ STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW,
+ STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK,
+ STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT,
+ STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO,
+ STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT,
+} from 'calypso/landing/stepper/constants';
import { ONBOARD_STORE } from 'calypso/landing/stepper/stores';
-import { recordSubmitStep } from '../../analytics/record-submit-step';
+import {
+ recordStepNavigation,
+ type RecordStepNavigationParams,
+} from '../../analytics/record-step-navigation';
import type { Flow, Navigate, ProvidedDependencies, StepperStep } from '../../types';
interface Params< FlowSteps extends StepperStep[] > {
@@ -28,21 +37,80 @@ export const useStepNavigationWithTracking = ( {
useSelect( ( select ) => ( select( ONBOARD_STORE ) as OnboardSelect ).getIntent(), [] ) ?? '';
const tracksEventPropsFromFlow = flow.useTracksEventProps?.();
+ const handleRecordStepNavigation = useCallback(
+ ( {
+ event,
+ providedDependencies,
+ additionalProps,
+ }: Omit< RecordStepNavigationParams, 'step' | 'intent' | 'flow' | 'variant' > ) => {
+ recordStepNavigation( {
+ event,
+ intent,
+ flow: flow.name,
+ step: currentStepRoute,
+ variant: flow.variantSlug,
+ providedDependencies,
+ additionalProps,
+ } );
+ },
+ [ intent, currentStepRoute, flow ]
+ );
+
return useMemo(
() => ( {
- ...stepNavigation,
- submit: ( providedDependencies: ProvidedDependencies = {}, ...params: string[] ) => {
- recordSubmitStep(
- providedDependencies,
- intent,
- flow.name,
- currentStepRoute,
- flow.variantSlug,
- tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT ]
- );
- stepNavigation.submit?.( providedDependencies, ...params );
- },
+ ...( stepNavigation.submit && {
+ submit: ( providedDependencies: ProvidedDependencies = {}, ...params: string[] ) => {
+ handleRecordStepNavigation( {
+ event: STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT,
+ providedDependencies,
+ additionalProps: tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT ],
+ } );
+ stepNavigation.submit?.( providedDependencies, ...params );
+ },
+ } ),
+ ...( stepNavigation.exitFlow && {
+ exitFlow: ( to: string ) => {
+ handleRecordStepNavigation( {
+ event: STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW,
+ additionalProps: {
+ to,
+ ...( tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW ] ?? {} ),
+ },
+ } );
+ stepNavigation.exitFlow?.( to );
+ },
+ } ),
+ ...( stepNavigation.goBack && {
+ goBack: () => {
+ handleRecordStepNavigation( {
+ event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK,
+ additionalProps: tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK ],
+ } );
+ stepNavigation.goBack?.();
+ },
+ } ),
+ ...( stepNavigation.goNext && {
+ goNext: () => {
+ handleRecordStepNavigation( {
+ event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT,
+ additionalProps: tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT ],
+ } );
+ stepNavigation.goNext?.();
+ },
+ } ),
+ ...( stepNavigation.goToStep && {
+ goToStep: ( step: string ) => {
+ handleRecordStepNavigation( {
+ event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO,
+ additionalProps: {
+ to: step,
+ ...( tracksEventPropsFromFlow?.[ STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO ] ?? {} ),
+ },
+ } );
+ stepNavigation.goToStep?.( step );
+ },
+ } ),
} ),
- [ stepNavigation, intent, flow, currentStepRoute, tracksEventPropsFromFlow ]
+ [ handleRecordStepNavigation, tracksEventPropsFromFlow, stepNavigation ]
);
};
diff --git a/client/landing/stepper/declarative-flow/internals/hooks/use-step-navigation-with-tracking/test/index.tsx b/client/landing/stepper/declarative-flow/internals/hooks/use-step-navigation-with-tracking/test/index.tsx
new file mode 100644
index 00000000000000..241795508f3a63
--- /dev/null
+++ b/client/landing/stepper/declarative-flow/internals/hooks/use-step-navigation-with-tracking/test/index.tsx
@@ -0,0 +1,154 @@
+/**
+ * @jest-environment jsdom
+ */
+import { renderHook, act } from '@testing-library/react';
+import {
+ STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW,
+ STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK,
+ STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT,
+ STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO,
+ STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT,
+} from '../../../../../constants';
+import { recordStepNavigation } from '../../../analytics/record-step-navigation';
+import { useStepNavigationWithTracking } from '../index';
+
+jest.mock( '@wordpress/data', () => ( {
+ useSelect: jest.fn(),
+} ) );
+jest.mock( 'calypso/landing/stepper/stores', () => ( {
+ ONBOARD_STORE: {},
+} ) );
+jest.mock( '../../../analytics/record-step-navigation', () => ( {
+ recordStepNavigation: jest.fn(),
+} ) );
+
+describe( 'useStepNavigationWithTracking', () => {
+ const stepNavControls = {
+ submit: jest.fn(),
+ exitFlow: jest.fn(),
+ goBack: jest.fn(),
+ goNext: jest.fn(),
+ goToStep: jest.fn(),
+ };
+
+ const mockParams = {
+ flow: {
+ name: 'mock-flow',
+ isSignupFlow: false,
+ useSteps: () => [],
+ useStepNavigation: () => stepNavControls,
+ },
+ currentStepRoute: 'mock-step',
+ navigate: () => {},
+ steps: [],
+ };
+
+ beforeEach( () => {
+ jest.clearAllMocks();
+ } );
+
+ it( 'returns callbacks for all known navigation controls', () => {
+ const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );
+
+ expect( result.current ).toHaveProperty( 'submit' );
+ expect( result.current ).toHaveProperty( 'exitFlow' );
+ expect( result.current ).toHaveProperty( 'goBack' );
+ expect( result.current ).toHaveProperty( 'goNext' );
+ expect( result.current ).toHaveProperty( 'goToStep' );
+ } );
+
+ it( 'calls the wrapped submit control with correct parameters and records the respective event', () => {
+ const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );
+ const providedDependencies = { foo: 'foo' };
+ act( () => {
+ result.current.submit?.( providedDependencies, 'bar', 'baz' );
+ } );
+
+ expect( stepNavControls.submit ).toHaveBeenCalledWith( providedDependencies, 'bar', 'baz' );
+ expect( recordStepNavigation ).toHaveBeenCalledWith( {
+ event: STEPPER_TRACKS_EVENT_STEP_NAV_SUBMIT,
+ intent: '',
+ flow: 'mock-flow',
+ step: 'mock-step',
+ variant: undefined,
+ providedDependencies,
+ additionalProps: undefined,
+ } );
+ } );
+
+ it( 'calls the wrapped goBack control with correct parameters and records the respective event', () => {
+ const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );
+
+ act( () => {
+ result.current.goBack?.();
+ } );
+
+ expect( stepNavControls.goBack ).toHaveBeenCalled();
+ expect( recordStepNavigation ).toHaveBeenCalledWith( {
+ event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_BACK,
+ intent: '',
+ flow: 'mock-flow',
+ step: 'mock-step',
+ variant: undefined,
+ providedDependencies: undefined,
+ additionalProps: undefined,
+ } );
+ } );
+
+ it( 'calls the wrapped goNext control with correct parameters and records the respective event', () => {
+ const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );
+
+ act( () => {
+ result.current.goNext?.();
+ } );
+
+ expect( stepNavControls.goNext ).toHaveBeenCalled();
+ expect( recordStepNavigation ).toHaveBeenCalledWith( {
+ event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_NEXT,
+ intent: '',
+ flow: 'mock-flow',
+ step: 'mock-step',
+ variant: undefined,
+ providedDependencies: undefined,
+ additionalProps: undefined,
+ } );
+ } );
+
+ it( 'calls the wrapped exitFlow control with correct parameters and records the respective event', () => {
+ const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );
+
+ act( () => {
+ result.current.exitFlow?.( 'to' );
+ } );
+
+ expect( stepNavControls.exitFlow ).toHaveBeenCalledWith( 'to' );
+ expect( recordStepNavigation ).toHaveBeenCalledWith( {
+ event: STEPPER_TRACKS_EVENT_STEP_NAV_EXIT_FLOW,
+ intent: '',
+ flow: 'mock-flow',
+ step: 'mock-step',
+ variant: undefined,
+ providedDependencies: undefined,
+ additionalProps: { to: 'to' },
+ } );
+ } );
+
+ it( 'calls the wrapped goToStep control with correct parameters and records the respective event', () => {
+ const { result } = renderHook( () => useStepNavigationWithTracking( mockParams ) );
+
+ act( () => {
+ result.current.goToStep?.( 'to' );
+ } );
+
+ expect( stepNavControls.goToStep ).toHaveBeenCalledWith( 'to' );
+ expect( recordStepNavigation ).toHaveBeenCalledWith( {
+ event: STEPPER_TRACKS_EVENT_STEP_NAV_GO_TO,
+ intent: '',
+ flow: 'mock-flow',
+ step: 'mock-step',
+ variant: undefined,
+ providedDependencies: undefined,
+ additionalProps: { to: 'to' },
+ } );
+ } );
+} );
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/courses/style.scss b/client/landing/stepper/declarative-flow/internals/steps-repository/courses/style.scss
index 0ffa10a700dbc6..5b79503f41d221 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/courses/style.scss
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/courses/style.scss
@@ -32,6 +32,17 @@ $grey-header: #151b1e;
}
}
+ &.has-navigation {
+ .step-container__content {
+ margin-top: -60px;
+ padding-top: 70px;
+
+ @include break-small {
+ margin-top: 0;
+ }
+ }
+ }
+
.step-container__content {
padding-top: 60px;
background-color: $grey-header;
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/create-site/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/create-site/index.tsx
index c99e665bcbfe5e..5a1b76b8e9c0d7 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/create-site/index.tsx
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/create-site/index.tsx
@@ -77,17 +77,32 @@ const CreateSite: Step = function CreateSite( { navigation, flow, data } ) {
const urlData = useSelector( getUrlData );
- const { domainItem, domainCartItem, planCartItem, selectedSiteTitle, productCartItems } =
- useSelect(
- ( select ) => ( {
- domainItem: ( select( ONBOARD_STORE ) as OnboardSelect ).getSelectedDomain(),
- domainCartItem: ( select( ONBOARD_STORE ) as OnboardSelect ).getDomainCartItem(),
- planCartItem: ( select( ONBOARD_STORE ) as OnboardSelect ).getPlanCartItem(),
- productCartItems: ( select( ONBOARD_STORE ) as OnboardSelect ).getProductCartItems(),
- selectedSiteTitle: ( select( ONBOARD_STORE ) as OnboardSelect ).getSelectedSiteTitle(),
- } ),
- []
- );
+ const {
+ domainItem,
+ domainCartItem,
+ domainCartItems = [],
+ planCartItem,
+ selectedSiteTitle,
+ productCartItems,
+ } = useSelect(
+ ( select ) => ( {
+ domainItem: ( select( ONBOARD_STORE ) as OnboardSelect ).getSelectedDomain(),
+ domainCartItem: ( select( ONBOARD_STORE ) as OnboardSelect ).getDomainCartItem(),
+ domainCartItems: ( select( ONBOARD_STORE ) as OnboardSelect ).getDomainCartItems(),
+ planCartItem: ( select( ONBOARD_STORE ) as OnboardSelect ).getPlanCartItem(),
+ productCartItems: ( select( ONBOARD_STORE ) as OnboardSelect ).getProductCartItems(),
+ selectedSiteTitle: ( select( ONBOARD_STORE ) as OnboardSelect ).getSelectedSiteTitle(),
+ } ),
+ []
+ );
+
+ /**
+ * Support singular and multiple domain cart items.
+ */
+ const mergedDomainCartItems = domainCartItems.slice( 0 );
+ if ( domainCartItem ) {
+ mergedDomainCartItems.push( domainCartItem );
+ }
const username = useSelector( getCurrentUserName );
@@ -128,7 +143,9 @@ const CreateSite: Step = function CreateSite( { navigation, flow, data } ) {
}
}
- const isPaidDomainItem = Boolean( domainCartItem?.product_slug );
+ const isPaidDomainItem = Boolean(
+ domainCartItem?.product_slug || domainCartItems?.some( ( el ) => el.product_slug )
+ );
const progress = useSelect(
( select ) => ( select( ONBOARD_STORE ) as OnboardSelect ).getProgress(),
@@ -195,8 +212,8 @@ const CreateSite: Step = function CreateSite( { navigation, flow, data } ) {
'#113AF5',
useThemeHeadstart,
username,
+ mergedDomainCartItems,
domainItem,
- domainCartItem,
sourceSlug
);
@@ -223,6 +240,10 @@ const CreateSite: Step = function CreateSite( { navigation, flow, data } ) {
await addProductsToCart( site.siteSlug, flow, productCartItems );
}
+ if ( domainCartItems?.length && site?.siteSlug ) {
+ await addProductsToCart( site.siteSlug, flow, productCartItems );
+ }
+
if ( isImportFocusedFlow( flow ) && site?.siteSlug && sourceMigrationStatus?.source_blog_id ) {
// Store temporary target blog id to source site option
addTempSiteToSourceOption( site.siteId, sourceMigrationStatus?.source_blog_id );
@@ -231,7 +252,7 @@ const CreateSite: Step = function CreateSite( { navigation, flow, data } ) {
return {
siteId: site?.siteId,
siteSlug: site?.siteSlug,
- goToCheckout: Boolean( planCartItem ),
+ goToCheckout: Boolean( planCartItem || mergedDomainCartItems.length ),
hasSetPreselectedTheme: Boolean( preselectedThemeSlug ),
siteCreated: true,
};
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/design-choices/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/design-choices/index.tsx
index b8a2d9c9598a24..e02f49ae4dcee7 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/design-choices/index.tsx
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/design-choices/index.tsx
@@ -1,7 +1,7 @@
import {
getAssemblerDesign,
themesIllustrationImage,
- assemblerIllustrationImage,
+ assemblerIllustrationV2Image,
hiBigSky,
} from '@automattic/design-picker';
import { StepContainer } from '@automattic/onboarding';
@@ -89,8 +89,10 @@ const DesignChoicesStep: Step = ( { navigation, flow, stepName } ) => {
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/design-choices/style.scss b/client/landing/stepper/declarative-flow/internals/steps-repository/design-choices/style.scss
index ee388db883f7a3..14fbe23fb0b9f4 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/design-choices/style.scss
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/design-choices/style.scss
@@ -37,7 +37,7 @@
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
- width: 340px;
+ width: 310px;
max-width: unset;
padding: 32px 18px 28px;
}
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/style.scss b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/style.scss
index 84284cab26bca3..dff00d253e0d22 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/style.scss
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/style.scss
@@ -643,11 +643,6 @@ $design-button-primary-color: rgb(17, 122, 201);
// Free flow
.update-design.design-setup,
.free.design-setup {
- .step-container:not(.design-setup__preview) {
- .navigation-link {
- display: none;
- }
- }
.step-container__navigation {
.step-container__skip-wrapper {
button.step-container__navigation-link {
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/unified-design-picker.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/unified-design-picker.tsx
index 9fc5af56e7f250..72eab0154781fc 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/unified-design-picker.tsx
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/unified-design-picker.tsx
@@ -45,7 +45,7 @@ import {
import ThemeTierBadge from 'calypso/components/theme-tier/theme-tier-badge';
import { ThemeUpgradeModal as UpgradeModal } from 'calypso/components/theme-upgrade-modal';
import { useIsSiteAssemblerEnabledExp } from 'calypso/data/site-assembler';
-import { ActiveTheme } from 'calypso/data/themes/use-active-theme-query';
+import { ActiveTheme, useActiveThemeQuery } from 'calypso/data/themes/use-active-theme-query';
import { recordTracksEvent } from 'calypso/lib/analytics/tracks';
import { useExperiment } from 'calypso/lib/explat';
import { urlToSlug } from 'calypso/lib/url';
@@ -128,6 +128,7 @@ const UnifiedDesignPickerStep: Step = ( { navigation, flow, stepName } ) => {
const siteTitle = site?.name;
const siteDescription = site?.description;
const { shouldLimitGlobalStyles } = useSiteGlobalStylesStatus( site?.ID );
+ const { data: siteActiveTheme } = useActiveThemeQuery( site?.ID ?? 0, !! site?.ID );
const isSiteAssemblerEnabled = useIsSiteAssemblerEnabledExp( 'design-picker' );
@@ -927,6 +928,8 @@ const UnifiedDesignPickerStep: Step = ( { navigation, flow, stepName } ) => {
getBadge={ getBadge }
oldHighResImageLoading={ oldHighResImageLoading }
isSiteAssemblerEnabled={ isSiteAssemblerEnabled }
+ siteActiveTheme={ siteActiveTheme?.[ 0 ]?.stylesheet ?? null }
+ showActiveThemeBadge={ intent !== 'build' }
/>
);
@@ -941,7 +944,7 @@ const UnifiedDesignPickerStep: Step = ( { navigation, flow, stepName } ) => {
stepContent={ stepContent }
recordTracksEvent={ recordStepContainerTracksEvent }
goNext={ handleSubmit }
- goBack={ handleBackClick }
+ goBack={ intent === 'update-design' ? submit : handleBackClick }
/>
);
};
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/plans/plans-wrapper.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/plans/plans-wrapper.tsx
index f4a1994d5222d3..18043bbc83333f 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/plans/plans-wrapper.tsx
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/plans/plans-wrapper.tsx
@@ -67,6 +67,7 @@ const PlansWrapper: React.FC< Props > = ( props ) => {
return {
hideFreePlan: ( select( ONBOARD_STORE ) as OnboardSelect ).getHideFreePlan(),
domainCartItem: ( select( ONBOARD_STORE ) as OnboardSelect ).getDomainCartItem(),
+ domainCartItems: ( select( ONBOARD_STORE ) as OnboardSelect ).getDomainCartItems(),
hidePlansFeatureComparison: (
select( ONBOARD_STORE ) as OnboardSelect
).getHidePlansFeatureComparison(),
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/index.tsx
index 63cc2ffb2e5b97..182f450327799c 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/index.tsx
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/index.tsx
@@ -1,8 +1,9 @@
import { FormLabel } from '@automattic/components';
import Card from '@automattic/components/src/card';
import { NextButton, StepContainer } from '@automattic/onboarding';
-import { Icon } from '@wordpress/components';
-import { seen, unseen } from '@wordpress/icons';
+import { Icon, Button } from '@wordpress/components';
+import { seen, unseen, chevronDown, chevronUp } from '@wordpress/icons';
+import { useI18n } from '@wordpress/react-i18n';
import clsx from 'clsx';
import { useTranslate } from 'i18n-calypso';
import { useEffect, useState, type FC } from 'react';
@@ -14,6 +15,7 @@ import FormattedHeader from 'calypso/components/formatted-header';
import FormRadio from 'calypso/components/forms/form-radio';
import FormTextInput from 'calypso/components/forms/form-text-input';
import FormTextArea from 'calypso/components/forms/form-textarea';
+import { useFlowLocale } from 'calypso/landing/stepper/hooks/use-flow-locale';
import { useQuery } from 'calypso/landing/stepper/hooks/use-query';
import { recordTracksEvent } from 'calypso/lib/analytics/tracks';
import { isValidUrl } from 'calypso/lib/importer/url-validation';
@@ -40,8 +42,13 @@ const mapApiError = ( error: any ) => {
export const CredentialsForm: FC< CredentialsFormProps > = ( { onSubmit, onSkip } ) => {
const translate = useTranslate();
+ const { hasTranslation } = useI18n();
+ const locale = useFlowLocale();
const [ passwordHidden, setPasswordHidden ] = useState( true );
+ const [ showNotes, setShowNotes ] = useState(
+ ! ( locale === 'en' || hasTranslation( 'Special instructions' ) )
+ );
const toggleVisibilityClasses = clsx( {
'site-migration-credentials__form-password__toggle': true,
@@ -240,67 +247,74 @@ export const CredentialsForm: FC< CredentialsFormProps > = ( { onSubmit, onSkip
) }
-
-
-
- { translate( 'WordPress admin username' ) }
-
-
(
+
+
+ { translate( 'WordPress admin username' ) }
+
+ (
+ {
+ const trimmedValue = e.target.value.trim();
+ field.onChange( trimmedValue );
+ } }
+ onBlur={ ( e: any ) => {
+ field.onBlur();
+ e.target.value = e.target.value.trim();
+ } }
+ />
+ ) }
+ />
+
+
+
+
+ { translate( 'Password' ) }
+
+
(
+
{
- const trimmedValue = e.target.value.trim();
- field.onChange( trimmedValue );
- } }
- onBlur={ ( e: any ) => {
- field.onBlur();
- e.target.value = e.target.value.trim();
- } }
/>
- ) }
- />
-
-
-
- { translate( 'Password' ) }
-
-
(
-
-
- setPasswordHidden( ! passwordHidden ) }
- type="button"
- >
- { passwordHidden ? : }
-
-
- ) }
- />
-
+ setPasswordHidden( ! passwordHidden ) }
+ type="button"
+ >
+ { passwordHidden ? : }
+
+
+ ) }
+ />
{ ( errors.username || errors.password ) && (
@@ -339,7 +353,7 @@ export const CredentialsForm: FC< CredentialsFormProps > = ( { onSubmit, onSkip
{ errors.backupFileLocation?.message }
) }
-
+
{ translate(
"Upload your file to a service like Dropbox or Google Drive to get a link. Don't forget to make sure that anyone with the link can access it."
) }
@@ -348,28 +362,60 @@ export const CredentialsForm: FC< CredentialsFormProps > = ( { onSubmit, onSkip
) }
-
-
{ translate( 'Notes (optional)' ) }
-
(
-
+ { ( locale === 'en' || hasTranslation( 'Special instructions' ) ) && (
+ setShowNotes( ! showNotes ) }
+ data-testid="special-instructions"
+ >
+ { translate( 'Special instructions' ) }
+
- ) }
- />
+
+ ) }
+ { showNotes && (
+ <>
+
+ (
+
+ ) }
+ />
+
+ { errors?.notes && (
+
+ { errors.notes.message }
+
+ ) }
+ { ( locale === 'en' ||
+ hasTranslation(
+ "Please don't share any passwords or secure information in this field. We'll reach out to collect that information if you have any additional credentials to access your site."
+ ) ) && (
+
+ { translate(
+ "Please don't share any passwords or secure information in this field. We'll reach out to collect that information if you have any additional credentials to access your site."
+ ) }
+
+ ) }
+ >
+ ) }
- { errors?.notes && (
-
{ errors.notes.message }
- ) }
+
{ errors?.root && (
{ errors.root.message }
) }
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/style.scss b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/style.scss
index 57f0bfdda6030f..1703a6a5be7a70 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/style.scss
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/style.scss
@@ -35,6 +35,9 @@
margin-top: 1em;
flex: 1;
}
+ .site-migration-credentials__form-field--notes {
+ margin-top: 5px;
+ }
.site-migration-credentials__radio-group {
display: flex;
flex-direction: column;
@@ -61,8 +64,9 @@
}
}
.site-migration-credentials__form-note {
- margin-top: 1em;
color: var(--color-text-subtle);
+ font-size: 0.875rem;
+ font-style: italic;
}
.site-migration-credentials__form-error {
color: var(--color-error);
@@ -97,4 +101,21 @@
margin-top: 1em;
text-align: center;
}
+ .site-migration-credentials__special-instructions {
+ .components-button {
+ padding: 0;
+ height: auto;
+ font-size: 0.875rem;
+ font-weight: 600;
+ color: var(--color-text);
+ margin-top: 1rem;
+ }
+
+ .site-migration-credentials__special-instructions-icon {
+ fill: var(--color-text) !important;
+ }
+ }
+ .site-migration-credentials__backup-note {
+ margin-top: 0.25rem;
+ }
}
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/test/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/test/index.tsx
index 8e107ebca2ffb3..1e82af6c899c40 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/test/index.tsx
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-migration-credentials/test/index.tsx
@@ -41,7 +41,7 @@ describe( 'SiteMigrationCredentials', () => {
expect( screen.queryByText( 'Site address' ) ).toBeInTheDocument();
expect( screen.queryByText( 'WordPress admin username' ) ).toBeInTheDocument();
expect( screen.queryByText( 'Password' ) ).toBeInTheDocument();
- expect( screen.queryByText( 'Notes (optional)' ) ).toBeInTheDocument();
+ expect( screen.queryByText( 'Special instructions' ) ).toBeInTheDocument();
} );
it( 'does not show any error message by default', async () => {
@@ -110,10 +110,13 @@ describe( 'SiteMigrationCredentials', () => {
const submit = jest.fn();
render( { navigation: { submit } } );
+ const expandNotesButton = screen.getByTestId( 'special-instructions' );
+ await userEvent.click( expandNotesButton );
+
const addressInput = screen.getByLabelText( /Site address/ );
const usernameInput = screen.getByLabelText( /WordPress admin username/ );
const passwordInput = screen.getByLabelText( /Password/ );
- const notesInput = screen.getByLabelText( /Notes \(optional\)/ );
+ const notesInput = screen.getByTestId( 'special-instructions-textarea' );
siteAddress && ( await userEvent.type( addressInput, siteAddress ) );
username && ( await userEvent.type( usernameInput, username ) );
@@ -175,7 +178,11 @@ describe( 'SiteMigrationCredentials', () => {
const addressInput = screen.getByLabelText(
isBackupRequest ? /Backup file location/ : /Site address/
);
- const notesInput = screen.getByLabelText( /Notes \(optional\)/ );
+
+ const expandNotesButton = screen.getByTestId( 'special-instructions' );
+ await userEvent.click( expandNotesButton );
+
+ const notesInput = screen.getByTestId( 'special-instructions-textarea' );
await userEvent.type( addressInput, siteAddress );
username &&
@@ -281,10 +288,13 @@ describe( 'SiteMigrationCredentials', () => {
const submit = jest.fn();
render( { navigation: { submit } } );
+ const expandNotesButton = screen.getByTestId( 'special-instructions' );
+ await userEvent.click( expandNotesButton );
+
await userEvent.type( screen.getByLabelText( /Site address/ ), 'test.com' );
await userEvent.type( screen.getByLabelText( /WordPress admin username/ ), 'username' );
await userEvent.type( screen.getByLabelText( /Password/ ), 'password' );
- await userEvent.type( screen.getByLabelText( /Notes \(optional\)/ ), 'notes' );
+ await userEvent.type( screen.getByTestId( 'special-instructions-textarea' ), 'notes' );
( wpcomRequest as jest.Mock ).mockRejectedValue( errorResponse );
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/site-picker/site-picker.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/site-picker/site-picker.tsx
index 9e28de82c9fa61..66821bc8b1b2f3 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/site-picker/site-picker.tsx
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/site-picker/site-picker.tsx
@@ -86,7 +86,6 @@ const SitePicker = function SitePicker( props: Props ) {
{ ( selectedStatus.hiddenCount > 0 || sites.length > perPage ) && (
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/unified-domains/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/unified-domains/index.tsx
new file mode 100644
index 00000000000000..d06f0aacdb10c3
--- /dev/null
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/unified-domains/index.tsx
@@ -0,0 +1,105 @@
+import { PLAN_PERSONAL } from '@automattic/calypso-products';
+import { usePersistedState } from '@automattic/onboarding';
+import { withShoppingCart } from '@automattic/shopping-cart';
+import { localize } from 'i18n-calypso';
+import { isEmpty } from 'lodash';
+import { connect } from 'react-redux';
+import { recordUseYourDomainButtonClick } from 'calypso/components/domains/register-domain-step/analytics';
+import { planItem } from 'calypso/lib/cart-values/cart-items';
+import CalypsoShoppingCartProvider from 'calypso/my-sites/checkout/calypso-shopping-cart-provider';
+import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
+import { RenderDomainsStep, submitDomainStepSelection } from 'calypso/signup/steps/domains';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { DOMAINS_WITH_PLANS_ONLY } from 'calypso/state/current-user/constants';
+import {
+ currentUserHasFlag,
+ getCurrentUser,
+ getCurrentUserSiteCount,
+ isUserLoggedIn,
+} from 'calypso/state/current-user/selectors';
+import {
+ recordAddDomainButtonClick,
+ recordAddDomainButtonClickInMapDomain,
+ recordAddDomainButtonClickInTransferDomain,
+ recordAddDomainButtonClickInUseYourDomain,
+} from 'calypso/state/domains/actions';
+import { getAvailableProductsList } from 'calypso/state/products-list/selectors';
+import getSitesItems from 'calypso/state/selectors/get-sites-items';
+import { fetchUsernameSuggestion } from 'calypso/state/signup/optional-dependencies/actions';
+import { removeStep } from 'calypso/state/signup/progress/actions';
+import { setDesignType } from 'calypso/state/signup/steps/design-type/actions';
+import { getDesignType } from 'calypso/state/signup/steps/design-type/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+import { ProvidedDependencies, StepProps } from '../../types';
+
+const RenderDomainsStepConnect = connect(
+ ( state, { flow }: StepProps ) => {
+ const productsList = getAvailableProductsList( state );
+ const productsLoaded = ! isEmpty( productsList );
+ const selectedSite = getSelectedSite( state );
+ const multiDomainDefaultPlan = planItem( PLAN_PERSONAL );
+ const userLoggedIn = isUserLoggedIn( state as object );
+ const currentUserSiteCount = getCurrentUserSiteCount( state as object );
+ const stepSectionName = window.location.pathname.includes( 'use-your-domain' )
+ ? 'use-your-domain'
+ : undefined;
+
+ return {
+ designType: getDesignType( state ),
+ currentUser: getCurrentUser( state as object ),
+ productsList,
+ productsLoaded,
+ selectedSite,
+ isDomainOnly: false,
+ sites: getSitesItems( state ),
+ userSiteCount: currentUserSiteCount,
+ previousStepName: 'user',
+ isPlanSelectionAvailableLaterInFlow: true,
+ userLoggedIn,
+ multiDomainDefaultPlan,
+ domainsWithPlansOnly: currentUserHasFlag( state as object, DOMAINS_WITH_PLANS_ONLY ),
+ flowName: flow,
+ path: window.location.pathname,
+ positionInFlow: 1,
+ isReskinned: true,
+ stepSectionName,
+ };
+ },
+ {
+ recordAddDomainButtonClick,
+ recordAddDomainButtonClickInMapDomain,
+ recordAddDomainButtonClickInTransferDomain,
+ recordAddDomainButtonClickInUseYourDomain,
+ recordUseYourDomainButtonClick,
+ removeStep,
+ submitDomainStepSelection,
+ setDesignType,
+ recordTracksEvent,
+ fetchUsernameSuggestion,
+ }
+)( withCartKey( withShoppingCart( localize( RenderDomainsStep ) ) ) );
+
+export default function DomainsStep( props: StepProps ) {
+ const [ stepState, setStepState ] = usePersistedState< ProvidedDependencies >();
+
+ return (
+
+ window.location.assign( url ) }
+ saveSignupStep={ ( state: ProvidedDependencies ) =>
+ setStepState( { ...stepState, ...state } )
+ }
+ submitSignupStep={ ( state: ProvidedDependencies ) => {
+ setStepState( { ...stepState, ...state } );
+ } }
+ goToNextStep={ ( state: ProvidedDependencies ) =>
+ props.navigation.submit?.( { ...stepState, ...state } )
+ }
+ step={ stepState }
+ flowName={ props.flow }
+ useStepperWrapper
+ />
+
+ );
+}
diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/use-my-domain/index.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/use-my-domain/index.tsx
index 76e0ba2f9e3732..d3cb9cf50a5ceb 100644
--- a/client/landing/stepper/declarative-flow/internals/steps-repository/use-my-domain/index.tsx
+++ b/client/landing/stepper/declarative-flow/internals/steps-repository/use-my-domain/index.tsx
@@ -2,6 +2,7 @@
import {
DESIGN_FIRST_FLOW,
START_WRITING_FLOW,
+ ONBOARDING_FLOW,
StepContainer,
isStartWritingFlow,
} from '@automattic/onboarding';
@@ -70,6 +71,7 @@ const UseMyDomain: Step = function UseMyDomain( { navigation, flow } ) {
switch ( flow ) {
case START_WRITING_FLOW:
case DESIGN_FIRST_FLOW:
+ case ONBOARDING_FLOW:
return getBlogOnboardingFlowStepContent();
default:
return getDefaultStepContent();
diff --git a/client/landing/stepper/declarative-flow/migration/index.tsx b/client/landing/stepper/declarative-flow/migration/index.tsx
index 1a1b4f8d6222eb..d5efce93ed8876 100644
--- a/client/landing/stepper/declarative-flow/migration/index.tsx
+++ b/client/landing/stepper/declarative-flow/migration/index.tsx
@@ -294,7 +294,7 @@ export default {
useStepNavigation( currentStep, navigate ) {
const stepHandlers = useCreateStepHandlers( navigate, this );
- return stepHandlers[ currentStep ];
+ return stepHandlers[ currentStep ] || {};
},
useSideEffect( currentStep, navigate ) {
diff --git a/client/landing/stepper/declarative-flow/onboarding.ts b/client/landing/stepper/declarative-flow/onboarding.ts
index 52747ea550b596..cea94c1ab5cb53 100644
--- a/client/landing/stepper/declarative-flow/onboarding.ts
+++ b/client/landing/stepper/declarative-flow/onboarding.ts
@@ -19,7 +19,11 @@ const onboarding: Flow = {
return stepsWithRequiredLogin( [
{
slug: 'domains',
- asyncComponent: () => import( './internals/steps-repository/domains' ),
+ asyncComponent: () => import( './internals/steps-repository/unified-domains' ),
+ },
+ {
+ slug: 'use-my-domain',
+ asyncComponent: () => import( './internals/steps-repository/use-my-domain' ),
},
{
slug: 'plans',
@@ -45,6 +49,8 @@ const onboarding: Flow = {
useStepNavigation( currentStepSlug, navigate ) {
const flowName = this.name;
+ const { setDomain, setDomainCartItems } = useDispatch( ONBOARD_STORE );
+
const { domainCartItem, planCartItem } = useSelect(
( select: ( key: string ) => OnboardSelect ) => ( {
domainCartItem: select( ONBOARD_STORE ).getDomainCartItem(),
@@ -58,6 +64,13 @@ const onboarding: Flow = {
const submit = async ( providedDependencies: ProvidedDependencies = {} ) => {
switch ( currentStepSlug ) {
case 'domains':
+ setDomain( providedDependencies.suggestion );
+ setDomainCartItems( providedDependencies.domainCart );
+ if ( providedDependencies.navigateToUseMyDomain ) {
+ return navigate( 'use-my-domain' );
+ }
+ return navigate( 'plans' );
+ case 'use-my-domain':
return navigate( 'plans' );
case 'plans':
return navigate( 'create-site', undefined, true );
@@ -68,9 +81,9 @@ const onboarding: Flow = {
siteSlug: providedDependencies.siteSlug,
} );
persistSignupDestination( destination );
-
if ( providedDependencies.goToCheckout ) {
const siteSlug = providedDependencies.siteSlug as string;
+
if ( planCartItem && siteSlug && flowName ) {
await addPlanToCart( siteSlug, flowName, true, '', planCartItem );
}
@@ -100,6 +113,8 @@ const onboarding: Flow = {
const goBack = () => {
switch ( currentStepSlug ) {
+ case 'use-my-domain':
+ return navigate( 'domains' );
case 'plans':
return navigate( 'domains' );
default:
diff --git a/client/landing/stepper/declarative-flow/site-migration-flow.ts b/client/landing/stepper/declarative-flow/site-migration-flow.ts
index 63bcc14e50a931..d6452e4c23d559 100644
--- a/client/landing/stepper/declarative-flow/site-migration-flow.ts
+++ b/client/landing/stepper/declarative-flow/site-migration-flow.ts
@@ -311,6 +311,19 @@ const siteMigration: Flow = {
// Do it for me option.
if ( providedDependencies?.how === HOW_TO_MIGRATE_OPTIONS.DO_IT_FOR_ME ) {
+ if ( config.isEnabled( 'automated-migration/collect-credentials' ) ) {
+ return navigate(
+ addQueryArgs(
+ {
+ siteSlug,
+ from: fromQueryParam,
+ siteId,
+ },
+ STEPS.SITE_MIGRATION_CREDENTIALS.slug
+ )
+ );
+ }
+
return navigate( STEPS.SITE_MIGRATION_ASSISTED_MIGRATION.slug, {
siteId,
siteSlug,
diff --git a/client/landing/stepper/declarative-flow/test/hosted-site-migration-flow.tsx b/client/landing/stepper/declarative-flow/test/hosted-site-migration-flow.tsx
index 8eab4609657b32..117006ea3b0b4f 100644
--- a/client/landing/stepper/declarative-flow/test/hosted-site-migration-flow.tsx
+++ b/client/landing/stepper/declarative-flow/test/hosted-site-migration-flow.tsx
@@ -1,6 +1,7 @@
/**
* @jest-environment jsdom
*/
+import config from '@automattic/calypso-config';
import { PLAN_MIGRATION_TRIAL_MONTHLY } from '@automattic/calypso-products';
import { isCurrentUserLoggedIn } from '@automattic/data-stores/src/user/selectors';
import { waitFor } from '@testing-library/react';
@@ -180,6 +181,7 @@ describe( 'Hosted site Migration Flow', () => {
} );
it( 'migrate redirects from the how-to-migrate (do it for me) page to assisted migration page', () => {
+ config.disable( 'automated-migration/collect-credentials' );
const { runUseStepNavigationSubmit } = renderFlow( hostedSiteMigrationFlow );
runUseStepNavigationSubmit( {
@@ -196,6 +198,24 @@ describe( 'Hosted site Migration Flow', () => {
siteSlug: 'example.wordpress.com',
},
} );
+ config.enable( 'automated-migration/collect-credentials' );
+ } );
+
+ it( 'migrate redirects from the how-to-migrate (do it for me) page to credential collection step', () => {
+ const { runUseStepNavigationSubmit } = renderFlow( hostedSiteMigrationFlow );
+
+ runUseStepNavigationSubmit( {
+ currentStep: STEPS.SITE_MIGRATION_HOW_TO_MIGRATE.slug,
+ dependencies: {
+ destination: 'migrate',
+ how: HOW_TO_MIGRATE_OPTIONS.DO_IT_FOR_ME,
+ },
+ } );
+
+ expect( getFlowLocation() ).toEqual( {
+ path: `/${ STEPS.SITE_MIGRATION_CREDENTIALS.slug }?siteSlug=example.wordpress.com`,
+ state: null,
+ } );
} );
it( 'migrate redirects from the how-to-migrate (upgrade needed) page to site-migration-upgrade-plan step', () => {
diff --git a/client/landing/stepper/declarative-flow/test/site-migration-flow.tsx b/client/landing/stepper/declarative-flow/test/site-migration-flow.tsx
index 6d7358500421ba..f97eb38ad263f0 100644
--- a/client/landing/stepper/declarative-flow/test/site-migration-flow.tsx
+++ b/client/landing/stepper/declarative-flow/test/site-migration-flow.tsx
@@ -224,6 +224,7 @@ describe( 'Site Migration Flow', () => {
} );
it( 'migrate redirects from the how-to-migrate (do it for me) page to assisted migration page', () => {
+ config.disable( 'automated-migration/collect-credentials' );
const { runUseStepNavigationSubmit } = renderFlow( siteMigrationFlow );
runUseStepNavigationSubmit( {
@@ -240,6 +241,24 @@ describe( 'Site Migration Flow', () => {
siteSlug: 'example.wordpress.com',
},
} );
+ config.enable( 'automated-migration/collect-credentials' );
+ } );
+
+ it( 'migrate redirects from the how-to-migrate (do it for me) page to credential collection step', () => {
+ const { runUseStepNavigationSubmit } = renderFlow( siteMigrationFlow );
+
+ runUseStepNavigationSubmit( {
+ currentStep: STEPS.SITE_MIGRATION_HOW_TO_MIGRATE.slug,
+ dependencies: {
+ destination: 'migrate',
+ how: HOW_TO_MIGRATE_OPTIONS.DO_IT_FOR_ME,
+ },
+ } );
+
+ expect( getFlowLocation() ).toEqual( {
+ path: `/${ STEPS.SITE_MIGRATION_CREDENTIALS.slug }?siteSlug=example.wordpress.com`,
+ state: null,
+ } );
} );
it( 'migrate redirects from the how-to-migrate (upgrade needed) page to site-migration-upgrade-plan step', () => {
diff --git a/client/landing/stepper/index.tsx b/client/landing/stepper/index.tsx
index ea6c04c4986ada..df20ca49f7c953 100644
--- a/client/landing/stepper/index.tsx
+++ b/client/landing/stepper/index.tsx
@@ -4,7 +4,12 @@ import { initializeAnalytics } from '@automattic/calypso-analytics';
import { CurrentUser } from '@automattic/calypso-analytics/dist/types/utils/current-user';
import config from '@automattic/calypso-config';
import { User as UserStore } from '@automattic/data-stores';
-import { IMPORT_HOSTED_SITE_FLOW } from '@automattic/onboarding';
+import {
+ HOSTED_SITE_MIGRATION_FLOW,
+ MIGRATION_FLOW,
+ MIGRATION_SIGNUP_FLOW,
+ SITE_MIGRATION_FLOW,
+} from '@automattic/onboarding';
import { QueryClientProvider } from '@tanstack/react-query';
import { useDispatch } from '@wordpress/data';
import defaultCalypsoI18n from 'i18n-calypso';
@@ -26,6 +31,8 @@ import { getInitialState, getStateFromCache, persistOnChange } from 'calypso/sta
import { createQueryClient } from 'calypso/state/query-client';
import initialReducer from 'calypso/state/reducer';
import { setStore } from 'calypso/state/redux-store';
+import { setCurrentFlowName } from 'calypso/state/signup/flow/actions';
+import { setSelectedSiteId } from 'calypso/state/ui/actions';
import { FlowRenderer } from './declarative-flow/internals';
import { AsyncHelpCenter } from './declarative-flow/internals/components';
import 'calypso/components/environment-badge/style.scss';
@@ -37,6 +44,7 @@ import { enhanceFlowWithAuth } from './utils/enhanceFlowWithAuth';
import { startStepperPerformanceTracking } from './utils/performance-tracking';
import { WindowLocaleEffectManager } from './utils/window-locale-effect-manager';
import type { Flow } from './declarative-flow/internals/types';
+import type { AnyAction } from 'redux';
declare const window: AppWindow;
@@ -76,8 +84,15 @@ const getFlowFromURL = () => {
return fromPath || fromQuery;
};
+const HOTJAR_ENABLED_FLOWS = [
+ MIGRATION_FLOW,
+ SITE_MIGRATION_FLOW,
+ HOSTED_SITE_MIGRATION_FLOW,
+ MIGRATION_SIGNUP_FLOW,
+];
+
const initializeHotJar = ( flowName: string ) => {
- if ( flowName === IMPORT_HOSTED_SITE_FLOW ) {
+ if ( HOTJAR_ENABLED_FLOWS.includes( flowName ) ) {
addHotJarScript();
}
};
@@ -124,6 +139,11 @@ window.AppBoot = async () => {
const { default: rawFlow } = await flowLoader();
const flow = rawFlow.__experimentalUseBuiltinAuth ? enhanceFlowWithAuth( rawFlow ) : rawFlow;
+ // When re-using steps from /start, we need to set the current flow name in the redux store, since some depend on it.
+ reduxStore.dispatch( setCurrentFlowName( flow.name ) );
+ // Reset the selected site ID when the stepper is loaded.
+ reduxStore.dispatch( setSelectedSiteId( null ) as unknown as AnyAction );
+
const root = createRoot( document.getElementById( 'wpcom' ) as HTMLElement );
root.render(
diff --git a/client/layout/color-scheme/index.jsx b/client/layout/color-scheme/index.jsx
new file mode 100644
index 00000000000000..c9338a02ffa00f
--- /dev/null
+++ b/client/layout/color-scheme/index.jsx
@@ -0,0 +1,45 @@
+import { getAdminColor } from 'calypso/state/admin-color/selectors';
+import { getPreference } from 'calypso/state/preferences/selectors';
+import { getSelectedSiteId } from 'calypso/state/ui/selectors';
+
+export function getColorScheme( { state, isGlobalSidebarVisible, sectionName } ) {
+ if ( isGlobalSidebarVisible ) {
+ return 'global';
+ }
+ if ( sectionName === 'checkout' ) {
+ return null;
+ }
+ const calypsoColorScheme = getPreference( state, 'colorScheme' );
+ const siteId = getSelectedSiteId( state );
+ const siteColorScheme = getAdminColor( state, siteId );
+
+ return siteColorScheme ?? calypsoColorScheme;
+}
+
+export function refreshColorScheme( prevColorScheme, nextColorScheme ) {
+ if ( typeof document === 'undefined' ) {
+ return;
+ }
+ if ( prevColorScheme === nextColorScheme ) {
+ return;
+ }
+
+ const classList = document.querySelector( 'body' ).classList;
+
+ if ( prevColorScheme ) {
+ classList.remove( `is-${ prevColorScheme }` );
+ }
+
+ if ( nextColorScheme ) {
+ classList.add( `is-${ nextColorScheme }` );
+
+ const themeColor = getComputedStyle( document.body )
+ .getPropertyValue( '--color-masterbar-background' )
+ .trim();
+ const themeColorMeta = document.querySelector( 'meta[name="theme-color"]' );
+ // We only adjust the `theme-color` meta content value in case we set it in `componentDidMount`
+ if ( themeColorMeta && themeColorMeta.getAttribute( 'data-colorscheme' ) === 'true' ) {
+ themeColorMeta.content = themeColor;
+ }
+ }
+}
diff --git a/client/layout/index.jsx b/client/layout/index.jsx
index 6f6e6c18e375f4..63cf3594372c0f 100644
--- a/client/layout/index.jsx
+++ b/client/layout/index.jsx
@@ -35,7 +35,6 @@ import isReaderTagEmbedPage from 'calypso/lib/reader/is-reader-tag-embed-page';
import { getMessagePathForJITM } from 'calypso/lib/route';
import UserVerificationChecker from 'calypso/lib/user/verification-checker';
import { useSelector } from 'calypso/state';
-import { getAdminColor } from 'calypso/state/admin-color/selectors';
import { isOffline } from 'calypso/state/application/selectors';
import { isUserLoggedIn, getCurrentUser } from 'calypso/state/current-user/selectors';
import {
@@ -45,7 +44,6 @@ import {
} from 'calypso/state/global-sidebar/selectors';
import { isUserNewerThan, WEEK_IN_MILLISECONDS } from 'calypso/state/guided-tours/contexts';
import { getCurrentOAuth2Client } from 'calypso/state/oauth2-clients/ui/selectors';
-import { getPreference } from 'calypso/state/preferences/selectors';
import getCurrentQueryArguments from 'calypso/state/selectors/get-current-query-arguments';
import getIsBlazePro from 'calypso/state/selectors/get-is-blaze-pro';
import getPrimarySiteSlug from 'calypso/state/selectors/get-primary-site-slug';
@@ -63,6 +61,7 @@ import {
masterbarIsVisible,
} from 'calypso/state/ui/selectors';
import BodySectionCssClass from './body-section-css-class';
+import { getColorScheme, refreshColorScheme } from './color-scheme';
import GlobalNotifications from './global-notifications';
import LayoutLoader from './loader';
import { shouldLoadInlineHelp, handleScroll } from './utils';
@@ -245,70 +244,11 @@ class Layout extends Component {
this.setState( { isDesktop } );
} );
- this.refreshColorScheme( undefined, this.props.colorScheme );
+ refreshColorScheme( undefined, this.props.colorScheme );
}
- /**
- * Refresh the color scheme if
- * - the color scheme has changed
- * - the global sidebar is visible and the color scheme is not `global`
- * - the global sidebar was visible and is now hidden and the color scheme is not `global`
- * - the section changed to `checkout` or changes from 'checkout' to something else
- * @param prevProps object
- */
componentDidUpdate( prevProps ) {
- const willTransitionFromOrToCheckout =
- ( prevProps.sectionName === 'checkout' && this.props.sectionName !== 'checkout' ) ||
- ( prevProps.sectionName !== 'checkout' && this.props.sectionName === 'checkout' );
-
- if (
- prevProps.colorScheme !== this.props.colorScheme ||
- ( this.props.isGlobalSidebarVisible && this.props.colorScheme !== 'global' ) ||
- ( prevProps.isGlobalSidebarVisible &&
- ! this.props.isGlobalSidebarVisible &&
- this.props.colorScheme !== 'global' ) ||
- willTransitionFromOrToCheckout
- ) {
- this.refreshColorScheme( prevProps.colorScheme, this.props.colorScheme );
- }
- }
-
- refreshColorScheme( prevColorScheme, nextColorScheme ) {
- if ( ! config.isEnabled( 'me/account/color-scheme-picker' ) ) {
- return;
- }
-
- if ( typeof document !== 'undefined' ) {
- const classList = document.querySelector( 'body' ).classList;
- const globalColorScheme = 'global';
-
- if ( this.props.sectionName === 'checkout' ) {
- classList.remove( `is-${ prevColorScheme }` );
- return;
- }
-
- if ( this.props.isGlobalSidebarVisible ) {
- // Force the global color scheme when the global sidebar is visible.
- nextColorScheme = globalColorScheme;
- } else {
- // Revert back to user's color scheme when the global sidebar is gone.
- prevColorScheme = globalColorScheme;
- }
-
- classList.remove( `is-${ prevColorScheme }` );
- classList.add( `is-${ nextColorScheme }` );
-
- const themeColor = getComputedStyle( document.body )
- .getPropertyValue( '--color-masterbar-background' )
- .trim();
- const themeColorMeta = document.querySelector( 'meta[name="theme-color"]' );
- // We only adjust the `theme-color` meta content value in case we set it in `componentDidMount`
- if ( themeColorMeta && themeColorMeta.getAttribute( 'data-colorscheme' ) === 'true' ) {
- themeColorMeta.content = themeColor;
- }
- }
-
- // intentionally don't remove these in unmount
+ refreshColorScheme( prevProps.colorScheme, this.props.colorScheme );
}
renderMasterbar( loadHelpCenterIcon ) {
@@ -564,15 +504,16 @@ export default withCurrentRoute(
'comments',
].includes( sectionName );
const sidebarIsHidden = ! secondary || isWcMobileApp() || isDomainAndPlanPackageFlow;
+ const isGlobalSidebarVisible = shouldShowGlobalSidebar && ! sidebarIsHidden;
+
const userAllowedToHelpCenter =
config.isEnabled( 'calypso/help-center' ) && ! getIsOnboardingAffiliateFlow( state );
- const calypsoColorScheme = getPreference( state, 'colorScheme' );
- const siteColorScheme = getAdminColor( state, siteId ) ?? calypsoColorScheme;
- const colorScheme =
- shouldShowUnifiedSiteSidebar || config.isEnabled( 'layout/site-level-user-profile' )
- ? siteColorScheme
- : calypsoColorScheme;
+ const colorScheme = getColorScheme( {
+ state,
+ sectionName,
+ isGlobalSidebarVisible,
+ } );
return {
masterbarIsHidden,
@@ -606,7 +547,7 @@ export default withCurrentRoute(
sidebarIsCollapsed: sectionName !== 'reader' && getSidebarIsCollapsed( state ),
userAllowedToHelpCenter,
currentRoute,
- isGlobalSidebarVisible: shouldShowGlobalSidebar && ! sidebarIsHidden,
+ isGlobalSidebarVisible,
isGlobalSidebarCollapsed: shouldShowCollapsedGlobalSidebar && ! sidebarIsHidden,
isUnifiedSiteSidebarVisible: shouldShowUnifiedSiteSidebar && ! sidebarIsHidden,
isNewUser: isUserNewerThan( WEEK_IN_MILLISECONDS )( state ),
diff --git a/client/layout/logged-out.jsx b/client/layout/logged-out.jsx
index 2b606f93e8e99b..85112d82e68bec 100644
--- a/client/layout/logged-out.jsx
+++ b/client/layout/logged-out.jsx
@@ -193,19 +193,16 @@ const LayoutLoggedOut = ( {
'subscriptions',
'theme',
'themes',
- 'start-with',
].includes( sectionName ) &&
! isReaderTagPage &&
! isReaderSearchPage &&
! isReaderDiscoverPage
) {
const nonMonochromeSections = [ 'plugins' ];
- const whiteNavbarSections = [ 'start-with' ];
const className = clsx( {
'is-style-monochrome':
isEnabled( 'site-profiler/metrics' ) && ! nonMonochromeSections.includes( sectionName ),
- 'is-style-white': whiteNavbarSections.includes( sectionName ),
} );
masterbar = (
@@ -217,9 +214,6 @@ const LayoutLoggedOut = ( {
! nonMonochromeSections.includes( sectionName ) && {
logoColor: 'white',
} ) }
- { ...( whiteNavbarSections.includes( sectionName ) && {
- logoColor: 'black',
- } ) }
{ ...( sectionName === 'subscriptions' && { variant: 'minimal' } ) }
{ ...( sectionName === 'patterns' && {
startUrl: getPatternLibraryOnboardingUrl( locale, isLoggedIn ),
diff --git a/client/layout/masterbar/crowdsignal.scss b/client/layout/masterbar/crowdsignal.scss
index e75c233b85abf4..bf5f0c5bc19454 100644
--- a/client/layout/masterbar/crowdsignal.scss
+++ b/client/layout/masterbar/crowdsignal.scss
@@ -5,8 +5,8 @@
--color-primary: var(--studio-wordpress-blue-90);
--color-primary-dark: var(--studio-wordpress-blue-100);
- --color-accent: var(--studio-pink-50);
- --color-accent-dark: var(--studio-pink-70);
+ --color-accent: var(--studio-wordpress-blue);
+ --color-accent-dark: var(--studio-wordpress-blue-70);
--color-border: var(--studio-gray-5);
--color-surface: var(--studio-white);
diff --git a/client/layout/masterbar/logged-in.jsx b/client/layout/masterbar/logged-in.jsx
index 1123f86f884a6f..cdd4e56fb89937 100644
--- a/client/layout/masterbar/logged-in.jsx
+++ b/client/layout/masterbar/logged-in.jsx
@@ -4,6 +4,8 @@ import page from '@automattic/calypso-router';
import { PromptIcon } from '@automattic/command-palette';
import { Button, Popover } from '@automattic/components';
import { isWithinBreakpoint, subscribeIsWithinBreakpoint } from '@automattic/viewport';
+import { Button as WPButton } from '@wordpress/components';
+import { debounce } from '@wordpress/compose';
import { Icon, category } from '@wordpress/icons';
import { localize } from 'i18n-calypso';
import PropTypes from 'prop-types';
@@ -66,6 +68,7 @@ import Notifications from './masterbar-notifications/notifications-button';
const NEW_MASTERBAR_SHIPPING_DATE = new Date( 2022, 3, 14 ).getTime();
const MENU_POPOVER_PREFERENCE_KEY = 'dismissible-card-masterbar-collapsable-menu-popover';
+const READER_POPOVER_PREFERENCE_KEY = 'dismissible-card-masterbar-reader-popover';
const MOBILE_BREAKPOINT = '<480px';
const IS_RESPONSIVE_MENU_BREAKPOINT = '<782px';
@@ -78,6 +81,8 @@ class MasterbarLoggedIn extends Component {
isResponsiveMenu: isWithinBreakpoint( IS_RESPONSIVE_MENU_BREAKPOINT ),
// making the ref a state triggers a re-render when it changes (needed for popover)
menuBtnRef: null,
+ readerBtnRef: null,
+ readerPosition: null,
};
static propTypes = {
@@ -93,6 +98,7 @@ class MasterbarLoggedIn extends Component {
isCheckoutFailed: PropTypes.bool,
isInEditor: PropTypes.bool,
hasDismissedThePopover: PropTypes.bool,
+ hasDismissedReaderPopover: PropTypes.bool,
isUserNewerThanNewNavigation: PropTypes.bool,
loadHelpCenterIcon: PropTypes.bool,
isGlobalSidebarVisible: PropTypes.bool,
@@ -121,6 +127,27 @@ class MasterbarLoggedIn extends Component {
}
};
+ setupReaderPositionObserver() {
+ if ( this.props.sectionName !== 'home' && this.props.sectionGroup !== 'sites-dashboard' ) {
+ return;
+ }
+ const readerItem = document.querySelector( '.masterbar__reader' );
+ this.resizeObserver = new ResizeObserver(
+ debounce( () => {
+ const newRect = readerItem.getBoundingClientRect();
+ const readerPositionChanged =
+ ! this.lastReaderPosition ||
+ newRect.left !== this.lastReaderPosition.left ||
+ newRect.top !== this.lastReaderPosition.top;
+ if ( readerPositionChanged ) {
+ this.setState( { readerPosition: newRect } );
+ this.lastReaderPosition = newRect;
+ }
+ }, 100 )
+ );
+ this.resizeObserver.observe( readerItem );
+ }
+
componentDidMount() {
// Give a chance to direct URLs to open the sidebar on page load ( eg by clicking 'me' in wp-admin ).
const qryString = parse( document.location.search.replace( /^\?/, '' ) );
@@ -134,12 +161,20 @@ class MasterbarLoggedIn extends Component {
};
document.addEventListener( 'keydown', this.actionSearchShortCutListener );
this.subscribeToViewPortChanges();
+
+ // Observe if the position of the reader item has changed.
+ // Re-render to update the reader tooltip position.
+ this.setupReaderPositionObserver();
}
componentWillUnmount() {
document.removeEventListener( 'keydown', this.actionSearchShortCutListener );
this.unsubscribeToViewPortChanges?.();
this.unsubscribeResponsiveMenuViewPortChanges?.();
+
+ if ( this.resizeObserver ) {
+ this.resizeObserver.disconnect();
+ }
}
handleToggleMobileMenu = () => {
@@ -319,6 +354,10 @@ class MasterbarLoggedIn extends Component {
this.props.savePreference( MENU_POPOVER_PREFERENCE_KEY, true );
};
+ dismissReaderPopover = () => {
+ this.props.savePreference( READER_POPOVER_PREFERENCE_KEY, true );
+ };
+
renderCheckout() {
const {
isCheckoutPending,
@@ -520,30 +559,63 @@ class MasterbarLoggedIn extends Component {
}
renderReader() {
- const { translate } = this.props;
+ const { translate, sectionName, sectionGroup, isFetchingPrefs, hasDismissedReaderPopover } =
+ this.props;
+ const { readerBtnRef } = this.state;
return (
-
-
+
-
+
+
+ }
+ onClick={ this.clickReader }
+ isActive={ this.isActive( 'reader', true ) }
+ tooltip={ translate( 'Read the blogs and topics you follow' ) }
+ preloadSection={ this.preloadReader }
+ ref={ ( ref ) => ref !== readerBtnRef && this.setState( { readerBtnRef: ref } ) }
+ hasGlobalBorderStyle
+ />
+ { readerBtnRef && (
+
-
-
- }
- onClick={ this.clickReader }
- isActive={ this.isActive( 'reader', true ) }
- tooltip={ translate( 'Read the blogs and topics you follow' ) }
- preloadSection={ this.preloadReader }
- hasGlobalBorderStyle
- />
+
+ { translate( "We've moved the Reader!", {
+ comment: 'This is a popover title',
+ } ) }
+
+
+ { translate( 'Click the eyeglasses icon to check it out.' ) }
+
+
+
+ { translate( 'Got it', { comment: 'Got it, as in OK' } ) }
+
+
+
+ ) }
+ >
);
}
@@ -858,6 +930,7 @@ class MasterbarLoggedIn extends Component {
export default connect(
( state ) => {
const sectionGroup = getSectionGroup( state );
+ const sectionName = getSectionName( state );
// Falls back to using the user's primary site if no site has been selected
// by the user yet
@@ -881,6 +954,7 @@ export default connect(
siteAdminUrl: getSiteAdminUrl( state, siteId ),
siteHomeUrl: getSiteHomeUrl( state, siteId ),
sectionGroup,
+ sectionName,
domainOnlySite: isDomainOnlySite( state, siteId ),
hasNoSites: siteCount === 0,
user: getCurrentUser( state ),
@@ -894,6 +968,7 @@ export default connect(
isJetpackNotAtomic: isJetpackSite( state, siteId ) && ! isAtomicSite( state, siteId ),
currentLayoutFocus: getCurrentLayoutFocus( state ),
hasDismissedThePopover: getPreference( state, MENU_POPOVER_PREFERENCE_KEY ),
+ hasDismissedReaderPopover: getPreference( state, READER_POPOVER_PREFERENCE_KEY ),
isFetchingPrefs: isFetchingPreferences( state ),
// If the user is newer than new navigation shipping date, don't tell them this nav is new. Everything is new to them.
isUserNewerThanNewNavigation:
diff --git a/client/layout/masterbar/masterbar-cart/masterbar-cart-count.scss b/client/layout/masterbar/masterbar-cart/masterbar-cart-count.scss
index dfc5826fd7852f..624e8b2bccd353 100644
--- a/client/layout/masterbar/masterbar-cart/masterbar-cart-count.scss
+++ b/client/layout/masterbar/masterbar-cart/masterbar-cart-count.scss
@@ -7,7 +7,7 @@
text-align: center;
min-width: 0.5rem;
border-radius: 50%;
- background-color: #c9356e;
+ background-color: var(--studio-wordpress-blue);
top: 5px;
position: relative;
diff --git a/client/layout/masterbar/style.scss b/client/layout/masterbar/style.scss
index bf27a175f37d3a..0612c479b0d86f 100644
--- a/client/layout/masterbar/style.scss
+++ b/client/layout/masterbar/style.scss
@@ -1491,3 +1491,56 @@ a.masterbar__quick-language-switcher {
opacity: 1;
}
}
+
+.popover.masterbar__reader-popover {
+ z-index: 176;
+
+ .popover__arrow::before {
+ --color-border-inverted: var(--color-neutral-100);
+ }
+
+ .popover__arrow {
+ border: 10px dashed var(--color-neutral-70) !important;
+ border-bottom-style: solid !important;
+ border-top: none !important;
+ border-left-color: transparent !important;
+ border-right-color: transparent !important;
+ }
+ .popover__inner {
+ display: flex;
+ gap: 16px;
+ padding: 16px;
+ flex-direction: column;
+ align-items: flex-start;
+ border-radius: 4px !important;
+ background-color: var(--color-neutral-100) !important;
+ border: 1px solid var(--color-neutral-70) !important;
+ left: 0 !important;
+ }
+
+ .masterbar__reader-popover-heading {
+ font-size: rem(16px);
+ color: var(--color-text-inverted);
+ font-weight: 500;
+ }
+
+ .masterbar__reader-popover-description {
+ max-width: 325px;
+ margin-top: -8px;
+ margin-bottom: 0;
+ font-size: rem(13px);
+ color: var(--color-neutral-0);
+ text-align: left;
+ }
+
+ .masterbar__reader-popover-actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ width: 100%;
+ button {
+ padding: 4px 8px;
+ font-size: rem(13px);
+ }
+ }
+}
diff --git a/client/lib/checkout/payment-methods.tsx b/client/lib/checkout/payment-methods.tsx
index b781946830a3c2..8b6a4a40611203 100644
--- a/client/lib/checkout/payment-methods.tsx
+++ b/client/lib/checkout/payment-methods.tsx
@@ -65,6 +65,7 @@ export interface StoredPaymentMethodCard extends StoredPaymentMethodBase {
card_iin: string;
card_last_4: string;
card_zip: string;
+ display_brand: string | null;
}
export interface StoredPaymentMethodEbanx extends StoredPaymentMethodBase {
@@ -117,7 +118,7 @@ interface ImagePathsMap {
const CREDIT_CARD_SELECTED_PATHS: ImagePathsMap = {
amex: creditCardAmexImage,
- cb: creditCardCartesBancairesImage,
+ cartes_bancaires: creditCardCartesBancairesImage,
diners: creditCardDinersImage,
discover: creditCardDiscoverImage,
jcb: creditCardJCBImage,
@@ -160,8 +161,7 @@ export const PaymentMethodSummary = ( {
displayType = translate( 'American Express' );
break;
- case 'cartes bancaires':
- case 'cb':
+ case 'cartes_bancaires':
displayType = translate( 'Cartes Bancaires' );
break;
diff --git a/client/lib/purchases/index.ts b/client/lib/purchases/index.ts
index d474f2d578db1b..d9ebae4e99f1fa 100644
--- a/client/lib/purchases/index.ts
+++ b/client/lib/purchases/index.ts
@@ -851,12 +851,15 @@ export function subscribedWithinPastWeek( purchase: Purchase ) {
/**
* Returns the payment logo to display based on the payment method
+ * 'displayBrand' respects the customer's card brand choice if available
* @param {Object} purchase - the purchase with which we are concerned
* @returns {string|null} the payment logo type, or null if no payment type is set.
*/
export function paymentLogoType( purchase: Purchase ): string | null | undefined {
if ( isPaidWithCreditCard( purchase ) ) {
- return purchase.payment.creditCard?.type;
+ return purchase.payment.creditCard?.displayBrand
+ ? purchase.payment.creditCard?.displayBrand
+ : purchase.payment.creditCard?.type;
}
if ( isPaidWithPayPalDirect( purchase ) ) {
diff --git a/client/login/magic-login/index.jsx b/client/login/magic-login/index.jsx
index 2606efdfaf7580..7e69949a6c2ac7 100644
--- a/client/login/magic-login/index.jsx
+++ b/client/login/magic-login/index.jsx
@@ -612,6 +612,9 @@ class MagicLogin extends Component {
const eventOptions = { client_id: oauth2Client.id, client_name: oauth2Client.title };
const isFromGravatar3rdPartyApp =
isGravatarOAuth2Client( oauth2Client ) && query?.gravatar_from === '3rd-party';
+ const isGravatarFlowWithEmail = !! (
+ isGravatarFlowOAuth2Client( oauth2Client ) && query?.email_address
+ );
this.emailToSha256( usernameOrEmail ).then( ( email ) =>
this.setState( { hashedEmail: email } )
@@ -713,7 +716,7 @@ class MagicLogin extends Component {
{ translate( 'Continue' ) }
- { ! isFromGravatar3rdPartyApp && (
+ { ! isFromGravatar3rdPartyApp && ! isGravatarFlowWithEmail && (
{ translate( 'Switch email' ) }
@@ -746,6 +749,9 @@ class MagicLogin extends Component {
} = this.state;
const isFromGravatar3rdPartyApp =
isGravatarOAuth2Client( oauth2Client ) && query?.gravatar_from === '3rd-party';
+ const isGravatarFlowWithEmail = !! (
+ isGravatarFlowOAuth2Client( oauth2Client ) && query?.email_address
+ );
const isProcessingCode = isValidatingCode || isCodeValidated;
let errorText = translate( 'Something went wrong. Please try again.' );
@@ -844,7 +850,7 @@ class MagicLogin extends Component {
args: { countdown: resendEmailCountdown },
} ) }
- { ! isFromGravatar3rdPartyApp && (
+ { ! isFromGravatar3rdPartyApp && ! isGravatarFlowWithEmail && (
{
this.resetResendEmailCountdown();
@@ -947,11 +953,13 @@ class MagicLogin extends Component {
const { isRequestingEmail, requestEmailErrorMessage } = this.state;
const isGravatarFlow = isGravatarFlowOAuth2Client( oauth2Client );
+ const isGravatarFlowWithEmail = !! ( isGravatarFlow && query?.email_address );
const isGravatar = isGravatarOAuth2Client( oauth2Client );
const isWPJobManager = isWPJobManagerOAuth2Client( oauth2Client );
const isFromGravatarSignup = isGravatar && query?.gravatar_from === 'signup';
const isFromGravatar3rdPartyApp = isGravatar && query?.gravatar_from === '3rd-party';
- const isEmailInputDisabled = isFromGravatar3rdPartyApp || isRequestingEmail;
+ const isEmailInputDisabled =
+ isFromGravatar3rdPartyApp || isRequestingEmail || isGravatarFlowWithEmail;
const submitButtonLabel = isGravatar
? translate( 'Continue' )
: translate( 'Send me sign in link' );
@@ -961,6 +969,7 @@ class MagicLogin extends Component {
oauth2ClientId: query?.client_id,
gravatarFrom: query?.gravatar_from,
gravatarFlow: isGravatarFlow,
+ emailAddress: query?.email_address,
} );
let headerText = isFromGravatarSignup
? translate( 'Create your Profile' )
diff --git a/client/login/wp-login/index.jsx b/client/login/wp-login/index.jsx
index 8e3c868830d109..7f0d3009f152bc 100644
--- a/client/login/wp-login/index.jsx
+++ b/client/login/wp-login/index.jsx
@@ -245,13 +245,16 @@ export class Login extends Component {
const isGravatar = isGravatarOAuth2Client( oauth2Client );
const isFromGravatar3rdPartyApp = isGravatar && currentQuery?.gravatar_from === '3rd-party';
+ const isGravatarFlow = isGravatarFlowOAuth2Client( oauth2Client );
+ const isGravatarFlowWithEmail = !! ( isGravatarFlow && currentQuery?.email_address );
const magicLoginUrl = login( {
locale,
twoFactorAuthType: 'link',
oauth2ClientId: currentQuery?.client_id,
redirectTo: currentQuery?.redirect_to,
gravatarFrom: currentQuery?.gravatar_from,
- gravatarFlow: isGravatarFlowOAuth2Client( oauth2Client ),
+ gravatarFlow: isGravatarFlow,
+ emailAddress: currentQuery?.email_address,
} );
const currentUrl = new URL( window.location.href );
currentUrl.searchParams.append( 'lostpassword_flow', true );
@@ -286,7 +289,7 @@ export class Login extends Component {
>
{ translate( 'Lost your password?' ) }
- { ! isFromGravatar3rdPartyApp && (
+ { ! isFromGravatar3rdPartyApp && ! isGravatarFlowWithEmail && (
{ translate( 'You have no account yet? {{signupLink}}Create one{{/signupLink}}.', {
components: {
diff --git a/client/login/wp-login/style.scss b/client/login/wp-login/style.scss
index cd8b0e3e7155c6..acb1eb0b588d4a 100644
--- a/client/login/wp-login/style.scss
+++ b/client/login/wp-login/style.scss
@@ -261,8 +261,8 @@ $image-height: 47px;
.auth-form__social-buttons {
.social-buttons__button {
- border: 1px solid var(--studio-pink-50);
- color: var(--studio-pink-50);
+ border: 1px solid var(--studio-wordpress-blue);
+ color: var(--studio-wordpress-blue);
box-shadow: none;
}
}
diff --git a/client/me/purchases/add-new-payment-method/index.tsx b/client/me/purchases/add-new-payment-method/index.tsx
index 0c0f8cd2e2d8e3..c982757ea42a11 100644
--- a/client/me/purchases/add-new-payment-method/index.tsx
+++ b/client/me/purchases/add-new-payment-method/index.tsx
@@ -19,6 +19,7 @@ import { paymentMethods } from 'calypso/me/purchases/paths';
import titles from 'calypso/me/purchases/titles';
import { useCreateCreditCard } from 'calypso/my-sites/checkout/src/hooks/use-create-payment-methods';
import { useSelector, useDispatch } from 'calypso/state';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
import { getCurrentUserLocale } from 'calypso/state/current-user/selectors';
import { errorNotice } from 'calypso/state/notices/actions';
import { PaymentMethodSelectorSubmitButtonContent } from '../manage-purchase/payment-method-selector/payment-method-selector-submit-button-content';
@@ -26,10 +27,12 @@ import { PaymentMethodSelectorSubmitButtonContent } from '../manage-purchase/pay
function AddNewPaymentMethod() {
const goToPaymentMethods = () => page( paymentMethods );
const addPaymentMethodTitle = String( titles.addPaymentMethod );
+ const currency = useSelector( getCurrentUserCurrencyCode );
const translate = useTranslate();
const { isStripeLoading, stripeLoadingError } = useStripe();
const stripeMethod = useCreateCreditCard( {
+ currency,
isStripeLoading,
stripeLoadingError,
shouldUseEbanx: false,
diff --git a/client/me/purchases/billing-history/receipt.tsx b/client/me/purchases/billing-history/receipt.tsx
index 5dc9e1744d8ccd..bda9a807fbf682 100644
--- a/client/me/purchases/billing-history/receipt.tsx
+++ b/client/me/purchases/billing-history/receipt.tsx
@@ -212,7 +212,9 @@ function ReceiptPaymentMethod( { transaction }: { transaction: BillingTransactio
} else if ( 'XXXX' !== transaction.cc_num ) {
text = translate( '%(cardType)s ending in %(cardNum)s', {
args: {
- cardType: transaction.cc_type.toUpperCase(),
+ cardType:
+ transaction.cc_display_brand?.replace( '_', ' ' ).toUpperCase() ??
+ transaction.cc_type.toUpperCase(),
cardNum: transaction.cc_num,
},
} );
diff --git a/client/me/purchases/cancel-purchase/refund-information.jsx b/client/me/purchases/cancel-purchase/refund-information.jsx
index 22aafd450f7ff0..7dd430b044119f 100644
--- a/client/me/purchases/cancel-purchase/refund-information.jsx
+++ b/client/me/purchases/cancel-purchase/refund-information.jsx
@@ -1,10 +1,21 @@
+import { recordTracksEvent } from '@automattic/calypso-analytics';
import config from '@automattic/calypso-config';
import { isDomainRegistration, isDomainMapping } from '@automattic/calypso-products';
import { FormLabel } from '@automattic/components';
+import { HelpCenter } from '@automattic/data-stores';
+import { useChatStatus } from '@automattic/help-center/src/hooks';
import { localizeUrl } from '@automattic/i18n-utils';
-import { CALYPSO_CONTACT, UPDATE_NAMESERVERS } from '@automattic/urls';
+import Button from '@automattic/odie-client/src/components/button';
+import { UPDATE_NAMESERVERS } from '@automattic/urls';
+import {
+ useCanConnectToZendeskMessaging,
+ useZendeskMessagingAvailability,
+ useOpenZendeskMessaging,
+} from '@automattic/zendesk-client';
+import { useDispatch as useDataStoreDispatch } from '@wordpress/data';
import i18n from 'i18n-calypso';
import PropTypes from 'prop-types';
+import { useCallback } from 'react';
import { connect } from 'react-redux';
import FormCheckbox from 'calypso/components/forms/form-checkbox';
import FormRadio from 'calypso/components/forms/form-radio';
@@ -19,6 +30,9 @@ import {
} from 'calypso/lib/purchases';
import { getIncludedDomainPurchase } from 'calypso/state/purchases/selectors';
import { getDomainsBySiteId } from 'calypso/state/sites/domains/selectors';
+import './style.scss';
+
+const HELP_CENTER_STORE = HelpCenter.register();
const CancelPurchaseRefundInformation = ( {
purchase,
@@ -29,7 +43,7 @@ const CancelPurchaseRefundInformation = ( {
confirmCancelBundledDomain,
onCancelConfirmationStateChange,
} ) => {
- const { refundPeriodInDays } = purchase;
+ const { siteId, siteUrl, refundPeriodInDays } = purchase;
let text;
let showSupportLink = true;
const onCancelBundledDomainChange = ( event ) => {
@@ -39,6 +53,88 @@ const CancelPurchaseRefundInformation = ( {
confirmCancelBundledDomain: newCancelBundledDomainValue && confirmCancelBundledDomain,
} );
};
+ const { setShowHelpCenter, setNavigateToRoute, resetStore } =
+ useDataStoreDispatch( HELP_CENTER_STORE );
+ const { isEligibleForChat } = useChatStatus();
+ const { data: canConnectToZendeskMessaging } = useCanConnectToZendeskMessaging();
+ const { data: isMessagingAvailable } = useZendeskMessagingAvailability(
+ 'wpcom_messaging',
+ isEligibleForChat
+ );
+ const { openZendeskWidget, isOpeningZendeskWidget } = useOpenZendeskMessaging(
+ 'migration-error',
+ 'zendesk_support_chat_key',
+ isEligibleForChat
+ );
+
+ const getHelp = useCallback( () => {
+ if ( isMessagingAvailable && canConnectToZendeskMessaging ) {
+ openZendeskWidget( {
+ siteUrl: siteUrl,
+ siteId: siteId,
+ message: `${ status }: Import onboarding flow; migration failed`,
+ onSuccess: () => {
+ resetStore();
+ setShowHelpCenter( false );
+ },
+ } );
+ } else {
+ setNavigateToRoute( '/contact-options' );
+ setShowHelpCenter( true );
+ }
+ }, [
+ resetStore,
+ openZendeskWidget,
+ siteId,
+ isMessagingAvailable,
+ siteUrl,
+ canConnectToZendeskMessaging,
+ setNavigateToRoute,
+ setShowHelpCenter,
+ ] );
+
+ const ContactSupportLink = () => {
+ const onClick = () => {
+ recordTracksEvent( 'calypso_cancellation_help_button_click' );
+ getHelp();
+ };
+
+ return (
+
+ { ! isRefundable( purchase ) && maybeWithinRefundPeriod( purchase )
+ ? i18n.translate(
+ 'Have a question? Want to request a refund? {{contactLink}}Ask a Happiness Engineer!{{/contactLink}}',
+ {
+ components: {
+ contactLink: (
+
+ ),
+ },
+ }
+ )
+ : i18n.translate(
+ 'Have a question? {{contactLink}}Ask a Happiness Engineer!{{/contactLink}}',
+ {
+ components: {
+ contactLink: (
+
+ ),
+ },
+ }
+ ) }
+
+ );
+ };
const onConfirmCancelBundledDomainChange = ( event ) => {
onCancelConfirmationStateChange( {
@@ -361,27 +457,7 @@ const CancelPurchaseRefundInformation = ( {
{ text }
) }
- { showSupportLink && (
-
- { ! isRefundable( purchase ) && maybeWithinRefundPeriod( purchase )
- ? i18n.translate(
- 'Have a question? Want to request a refund? {{contactLink}}Ask a Happiness Engineer!{{/contactLink}}',
- {
- components: {
- contactLink: ,
- },
- }
- )
- : i18n.translate(
- 'Have a question? {{contactLink}}Ask a Happiness Engineer!{{/contactLink}}',
- {
- components: {
- contactLink: ,
- },
- }
- ) }
-
- ) }
+ { showSupportLink &&
}
);
};
diff --git a/client/me/purchases/cancel-purchase/style.scss b/client/me/purchases/cancel-purchase/style.scss
index 41ee1f54d03ab4..dcce1f292c2372 100644
--- a/client/me/purchases/cancel-purchase/style.scss
+++ b/client/me/purchases/cancel-purchase/style.scss
@@ -53,6 +53,12 @@
font-size: $font-body-small;
}
+.cancel-purchase__support-information .support-link {
+ color: var(--color-primary);
+ font-weight: 600;
+ padding-inline-start: 0;
+}
+
.cancel-purchase__site-title {
font-size: $font-body-extra-small;
text-transform: uppercase;
diff --git a/client/me/purchases/confirm-cancel-domain/index.jsx b/client/me/purchases/confirm-cancel-domain/index.jsx
index a05e0c434a5072..60d976aada6d1e 100644
--- a/client/me/purchases/confirm-cancel-domain/index.jsx
+++ b/client/me/purchases/confirm-cancel-domain/index.jsx
@@ -1,11 +1,12 @@
import { isDomainRegistration } from '@automattic/calypso-products';
import page from '@automattic/calypso-router';
import { Card, FormLabel } from '@automattic/components';
-import { localize } from 'i18n-calypso';
+import i18n, { getLocaleSlug, localize } from 'i18n-calypso';
import { map, find } from 'lodash';
import PropTypes from 'prop-types';
import { Component, Fragment } from 'react';
import { connect } from 'react-redux';
+import ActionPanelLink from 'calypso/components/action-panel/link';
import QueryUserPurchases from 'calypso/components/data/query-user-purchases';
import FormButton from 'calypso/components/forms/form-button';
import FormCheckbox from 'calypso/components/forms/form-checkbox';
@@ -122,12 +123,30 @@ class ConfirmCancelDomain extends Component {
}
if ( error ) {
- this.props.errorNotice(
- error.message ||
+ if (
+ getLocaleSlug() === 'en' ||
+ getLocaleSlug() === 'en-gb' ||
+ i18n.hasTranslation(
+ 'Unable to cancel your purchase. Please try again later or {{a}}contact support{{/a}}.'
+ )
+ ) {
+ this.props.errorNotice(
+ translate(
+ 'Unable to cancel your purchase. Please try again later or {{a}}contact support{{/a}}.',
+ {
+ components: {
+ a: ,
+ },
+ }
+ )
+ );
+ } else {
+ this.props.errorNotice(
translate(
'Unable to cancel your purchase. Please try again later or contact support.'
)
- );
+ );
+ }
return;
}
diff --git a/client/me/purchases/manage-purchase/change-payment-method/use-create-assignable-payment-methods.tsx b/client/me/purchases/manage-purchase/change-payment-method/use-create-assignable-payment-methods.tsx
index a702512816a009..35f517c130f8fb 100644
--- a/client/me/purchases/manage-purchase/change-payment-method/use-create-assignable-payment-methods.tsx
+++ b/client/me/purchases/manage-purchase/change-payment-method/use-create-assignable-payment-methods.tsx
@@ -9,6 +9,8 @@ import {
} from 'calypso/my-sites/checkout/src/hooks/use-create-payment-methods';
import { useStoredPaymentMethods } from 'calypso/my-sites/checkout/src/hooks/use-stored-payment-methods';
import { translateCheckoutPaymentMethodToWpcomPaymentMethod } from 'calypso/my-sites/checkout/src/lib/translate-payment-method-names';
+import { useSelector } from 'calypso/state';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
import { PaymentMethodSelectorSubmitButtonContent } from '../payment-method-selector/payment-method-selector-submit-button-content';
import useFetchAvailablePaymentMethods from './use-fetch-available-payment-methods';
import type { PaymentMethod } from '@automattic/composite-checkout';
@@ -26,6 +28,7 @@ export default function useCreateAssignablePaymentMethods(
): PaymentMethod[] {
const translate = useTranslate();
const { isStripeLoading, stripeLoadingError } = useStripe();
+ const currency = useSelector( getCurrentUserCurrencyCode );
const {
isFetching: isLoadingAllowedPaymentMethods,
@@ -47,6 +50,7 @@ export default function useCreateAssignablePaymentMethods(
} );
const hasExistingCardMethods = existingCardMethods && existingCardMethods.length > 0;
const stripeMethod = useCreateCreditCard( {
+ currency,
isStripeLoading,
stripeLoadingError,
shouldUseEbanx: false,
diff --git a/client/me/purchases/manage-purchase/style.scss b/client/me/purchases/manage-purchase/style.scss
index 6651edde380e44..97b6261a17ef13 100644
--- a/client/me/purchases/manage-purchase/style.scss
+++ b/client/me/purchases/manage-purchase/style.scss
@@ -342,6 +342,7 @@
.payment-logo {
margin-right: 5px;
+ border-radius: 2px;
}
a {
@@ -354,7 +355,7 @@
}
&.is-expiring .manage-purchase__detail-date-span {
- color: var(--studio-pink-50);
+ color: var(--studio-red);
}
}
diff --git a/client/me/purchases/payment-methods/payment-method-details.tsx b/client/me/purchases/payment-methods/payment-method-details.tsx
index 28d45fd1171544..9e36afbf8479ba 100644
--- a/client/me/purchases/payment-methods/payment-method-details.tsx
+++ b/client/me/purchases/payment-methods/payment-method-details.tsx
@@ -12,6 +12,7 @@ import 'calypso/me/purchases/payment-methods/style.scss';
interface Props {
lastDigits?: string;
+ displayBrand?: string | null;
cardType?: string;
name: string;
expiry?: string;
@@ -24,6 +25,7 @@ interface Props {
const PaymentMethodDetails: FunctionComponent< Props > = ( {
lastDigits,
+ displayBrand,
cardType,
name,
expiry,
@@ -39,7 +41,7 @@ const PaymentMethodDetails: FunctionComponent< Props > = ( {
const expirationDate = expiry ? moment( expiry, moment.ISO_8601, true ) : null;
const displayExpirationDate = expirationDate?.isValid() ? expirationDate.format( 'MM/YY' ) : null;
- const type = cardType?.toLocaleLowerCase() || paymentPartner || '';
+ const type = displayBrand ?? ( cardType?.toLocaleLowerCase() || paymentPartner || '' );
return (
diff --git a/client/me/purchases/payment-methods/payment-method.tsx b/client/me/purchases/payment-methods/payment-method.tsx
index f6c22fdf0f08ab..212ebe28d43e42 100644
--- a/client/me/purchases/payment-methods/payment-method.tsx
+++ b/client/me/purchases/payment-methods/payment-method.tsx
@@ -21,6 +21,9 @@ export default function PaymentMethod( { paymentMethod }: { paymentMethod: Store
{ purchase.payment.creditCard.number }
diff --git a/client/me/style.scss b/client/me/style.scss
index 7cda47ebfef403..6767c0d2246f69 100644
--- a/client/me/style.scss
+++ b/client/me/style.scss
@@ -35,7 +35,6 @@ body.is-section-me {
.main {
padding: 24px;
border-block-end: 1px solid var(--studio-gray-0);
- height: calc(100vh - var(--masterbar-height) - var(--content-padding-top) - var(--content-padding-bottom));
}
background: var(--color-surface);
border-radius: 8px; /* stylelint-disable-line scales/radii */
diff --git a/client/my-sites/checkout/checkout-main-wrapper.tsx b/client/my-sites/checkout/checkout-main-wrapper.tsx
index 2bffaa88037073..c865591001616b 100644
--- a/client/my-sites/checkout/checkout-main-wrapper.tsx
+++ b/client/my-sites/checkout/checkout-main-wrapper.tsx
@@ -23,6 +23,10 @@ const logCheckoutError = ( error: Error ) => {
const CheckoutMainWrapperStyles = styled.div`
background-color: ${ colorStudio.colors[ 'White' ] };
+
+ a {
+ color: ${ colorStudio.colors[ 'WordPress Blue 50' ] };
+ }
`;
export default function CheckoutMainWrapper( {
diff --git a/client/my-sites/checkout/src/components/akismet-pro-quantity-dropdown/index.tsx b/client/my-sites/checkout/src/components/akismet-pro-quantity-dropdown/index.tsx
index 8f12a89df05d8b..1b29c4209a74f2 100644
--- a/client/my-sites/checkout/src/components/akismet-pro-quantity-dropdown/index.tsx
+++ b/client/my-sites/checkout/src/components/akismet-pro-quantity-dropdown/index.tsx
@@ -73,11 +73,11 @@ const Option = styled.li`
cursor: pointer;
&:hover {
- background: #e9f0f5;
+ background: var( --studio-wordpress-blue-5 );
}
&.item-variant-option--selected {
- background: #055d9c;
+ background: var( --studio-wordpress-blue-50 );
color: white;
}
`;
diff --git a/client/my-sites/checkout/src/components/checkout-main-content.tsx b/client/my-sites/checkout/src/components/checkout-main-content.tsx
index 59c6846b780104..ef220ce83150e2 100644
--- a/client/my-sites/checkout/src/components/checkout-main-content.tsx
+++ b/client/my-sites/checkout/src/components/checkout-main-content.tsx
@@ -1144,6 +1144,10 @@ const WPCheckoutMainContent = styled.div`
padding: 0 24px 0 64px;
}
}
+
+ .editor-checkout-modal & {
+ margin-top: 20px;
+ }
`;
const WPCheckoutSidebarContent = styled.div`
@@ -1163,6 +1167,14 @@ const WPCheckoutSidebarContent = styled.div`
padding: 144px 64px 0 24px;
}
}
+
+ .editor-checkout-modal & {
+ padding: 68px 24px 144px 64px;
+
+ .rtl & {
+ padding: 68px 64px 0 24px;
+ }
+ }
`;
const SitePreviewWrapper = styled.div`
.home-site-preview {
diff --git a/client/my-sites/checkout/src/components/checkout-main.tsx b/client/my-sites/checkout/src/components/checkout-main.tsx
index 45f028682c0b52..721c48438af829 100644
--- a/client/my-sites/checkout/src/components/checkout-main.tsx
+++ b/client/my-sites/checkout/src/components/checkout-main.tsx
@@ -534,9 +534,9 @@ export default function CheckoutMain( {
primaryOver: colors[ 'Jetpack Green 60' ],
success: colors[ 'Jetpack Green' ],
discount: colors[ 'Jetpack Green' ],
- highlight: colors[ 'Blue 50' ],
- highlightBorder: colors[ 'Blue 80' ],
- highlightOver: colors[ 'Blue 60' ],
+ highlight: colors[ 'WordPress Blue 50' ],
+ highlightBorder: colors[ 'WordPress Blue 80' ],
+ highlightOver: colors[ 'WordPress Blue 60' ],
}
: {};
const theme = { ...checkoutTheme, colors: { ...checkoutTheme.colors, ...jetpackColors } };
diff --git a/client/my-sites/checkout/src/components/checkout-sidebar-plan-upsell/style.scss b/client/my-sites/checkout/src/components/checkout-sidebar-plan-upsell/style.scss
index d08f494ae03f58..8e02bc19e2992e 100644
--- a/client/my-sites/checkout/src/components/checkout-sidebar-plan-upsell/style.scss
+++ b/client/my-sites/checkout/src/components/checkout-sidebar-plan-upsell/style.scss
@@ -8,7 +8,7 @@
}
.promo-card.checkout-sidebar-plan-upsell .action-panel__title {
- color: var(--studio-blue-50);
+ color: var(--studio-wordpress-blue-50);
display: block;
font-size: $font-title-medium;
font-weight: 400;
diff --git a/client/my-sites/checkout/src/components/item-variation-picker/styles.tsx b/client/my-sites/checkout/src/components/item-variation-picker/styles.tsx
index 76b38962790244..ab9d329fecd34c 100644
--- a/client/my-sites/checkout/src/components/item-variation-picker/styles.tsx
+++ b/client/my-sites/checkout/src/components/item-variation-picker/styles.tsx
@@ -35,16 +35,17 @@ export const Option = styled.li< OptionProps >`
font-weight: ${ ( props ) => props.theme.weights.normal };
cursor: pointer;
flex-direction: row;
- justify-content: space-between; align-items: center;
+ justify-content: space-between;
+ align-items: center;
/* the calc aligns the price with the price in CurrentOption */
padding: 10px calc( 14px + 24px + 16px ) 10px 16px;
&:hover {
- var( --studio-blue-0 );
+ background: var( --studio-wordpress-blue-5 );
}
&.item-variant-option--selected {
- background: #055d9c;
+ background: var( --studio-wordpress-blue-50 );
color: #fff;
}
`;
diff --git a/client/my-sites/checkout/src/components/payment-logos.js b/client/my-sites/checkout/src/components/payment-logos.js
index 7e299405e763c5..a0ee45d8453c3c 100644
--- a/client/my-sites/checkout/src/components/payment-logos.js
+++ b/client/my-sites/checkout/src/components/payment-logos.js
@@ -29,44 +29,65 @@ VisaLogo.propTypes = {
};
export function CBLogo( { className } ) {
+ // We need to provide a unique ID to any svg that uses an id prop
+ // especially if we expect multiple instances of the component to render on the page
+ const uniqueID = `${ Math.floor( 10000 + Math.random() * 90000 ) }`;
+
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/client/my-sites/checkout/src/components/payment-method-logos.js b/client/my-sites/checkout/src/components/payment-method-logos.js
index 8bd42fe8a3f6a4..802cf3070fd85a 100644
--- a/client/my-sites/checkout/src/components/payment-method-logos.js
+++ b/client/my-sites/checkout/src/components/payment-method-logos.js
@@ -1,9 +1,11 @@
import styled from '@emotion/styled';
export const PaymentMethodLogos = styled.span`
+ display: flex;
flex: 1;
- transform: translateY( 3px );
text-align: right;
+ align-items: center;
+ justify-content: flex-end;
.rtl & {
text-align: left;
@@ -11,5 +13,9 @@ export const PaymentMethodLogos = styled.span`
svg {
display: inline-block;
+
+ &.has-background {
+ padding-inline-end: 5px;
+ }
}
`;
diff --git a/client/my-sites/checkout/src/hooks/use-create-payment-methods/index.tsx b/client/my-sites/checkout/src/hooks/use-create-payment-methods/index.tsx
index c4ecaeb5e9fefe..9b57e38f0840b7 100644
--- a/client/my-sites/checkout/src/hooks/use-create-payment-methods/index.tsx
+++ b/client/my-sites/checkout/src/hooks/use-create-payment-methods/index.tsx
@@ -65,6 +65,7 @@ export function useCreatePayPal( {
}
export function useCreateCreditCard( {
+ currency,
isStripeLoading,
stripeLoadingError,
shouldUseEbanx,
@@ -74,6 +75,7 @@ export function useCreateCreditCard( {
allowUseForAllSubscriptions,
hasExistingCardMethods,
}: {
+ currency: string | null;
isStripeLoading: boolean;
stripeLoadingError: StripeLoadingError;
shouldUseEbanx: boolean;
@@ -96,6 +98,7 @@ export function useCreateCreditCard( {
() =>
shouldLoadStripeMethod
? createCreditCardMethod( {
+ currency,
store: stripePaymentMethodStore,
shouldUseEbanx,
shouldShowTaxFields,
@@ -105,6 +108,7 @@ export function useCreateCreditCard( {
} )
: null,
[
+ currency,
shouldLoadStripeMethod,
stripePaymentMethodStore,
shouldUseEbanx,
@@ -399,7 +403,7 @@ export default function useCreatePaymentMethods( {
} ): PaymentMethod[] {
const cartKey = useCartKey();
const { responseCart } = useShoppingCart( cartKey );
-
+ const { currency } = responseCart;
const paypalMethod = useCreatePayPal( {} );
const idealMethod = useCreateIdeal( {
@@ -459,6 +463,7 @@ export default function useCreatePaymentMethods( {
// in the credit card form instead.
const shouldShowTaxFields = contactDetailsType === 'none';
const stripeMethod = useCreateCreditCard( {
+ currency,
shouldShowTaxFields,
isStripeLoading,
stripeLoadingError,
diff --git a/client/my-sites/checkout/src/hooks/use-create-payment-methods/use-create-existing-cards.ts b/client/my-sites/checkout/src/hooks/use-create-payment-methods/use-create-existing-cards.ts
index 3bb5471a83d97d..da93a6a6e94bc0 100644
--- a/client/my-sites/checkout/src/hooks/use-create-payment-methods/use-create-existing-cards.ts
+++ b/client/my-sites/checkout/src/hooks/use-create-payment-methods/use-create-existing-cards.ts
@@ -47,7 +47,9 @@ export default function useCreateExistingCards( {
id: `existingCard-${ storedDetails.stored_details_id }`,
cardholderName: storedDetails.name,
cardExpiry: storedDetails.expiry,
- brand: storedDetails.card_type,
+ brand: storedDetails?.display_brand
+ ? storedDetails.display_brand
+ : storedDetails.card_type,
last4: storedDetails.card_last_4,
storedDetailsId: storedDetails.stored_details_id,
paymentMethodToken: storedDetails.mp_ref,
diff --git a/client/my-sites/checkout/src/payment-methods/credit-card/assign-to-all-payment-methods.tsx b/client/my-sites/checkout/src/payment-methods/credit-card/assign-to-all-payment-methods.tsx
index e1b4a07e2b0f9b..f5f668fa6211fc 100644
--- a/client/my-sites/checkout/src/payment-methods/credit-card/assign-to-all-payment-methods.tsx
+++ b/client/my-sites/checkout/src/payment-methods/credit-card/assign-to-all-payment-methods.tsx
@@ -1,4 +1,4 @@
-import styled from '@emotion/styled';
+import { styled } from '@automattic/wpcom-checkout';
import { CheckboxControl } from '@wordpress/components';
import { useTranslate } from 'i18n-calypso';
import InlineSupportLink from 'calypso/components/inline-support-link';
@@ -7,6 +7,15 @@ import { recordTracksEvent } from 'calypso/state/analytics/actions';
const CheckboxWrapper = styled.div`
margin-top: 16px;
+
+ .assign-to-all-payment-methods-checkbox input[type='checkbox']:checked {
+ background: ${ ( props ) => props.theme.colors.primary };
+ border-color: ${ ( props ) => props.theme.colors.primary };
+ }
+
+ a.inline-support-link.assign-to-all-payment-methods-checkbox__link {
+ color: ${ ( props ) => props.theme.colors.primary };
+ }
`;
export default function AssignToAllPaymentMethods( {
@@ -34,6 +43,7 @@ export default function AssignToAllPaymentMethods( {
return (
diff --git a/client/my-sites/checkout/src/payment-methods/credit-card/contact-fields.tsx b/client/my-sites/checkout/src/payment-methods/credit-card/contact-fields.tsx
index 08fe7c650cef5a..a0d4a53d4be474 100644
--- a/client/my-sites/checkout/src/payment-methods/credit-card/contact-fields.tsx
+++ b/client/my-sites/checkout/src/payment-methods/credit-card/contact-fields.tsx
@@ -37,7 +37,7 @@ export default function ContactFields( {
};
return (
-
+ <>
{ shouldUseEbanx && ! shouldShowTaxFields && (
) }
-
+ >
);
}
diff --git a/client/my-sites/checkout/src/payment-methods/credit-card/credit-card-number-field.tsx b/client/my-sites/checkout/src/payment-methods/credit-card/credit-card-number-field.tsx
index 6f5e04f3d9870f..601ad49109c13f 100644
--- a/client/my-sites/checkout/src/payment-methods/credit-card/credit-card-number-field.tsx
+++ b/client/my-sites/checkout/src/payment-methods/credit-card/credit-card-number-field.tsx
@@ -1,5 +1,4 @@
import { FormStatus, useFormStatus } from '@automattic/composite-checkout';
-import { PaymentLogo } from '@automattic/wpcom-checkout';
import { CardNumberElement } from '@stripe/react-stripe-js';
import { useSelect } from '@wordpress/data';
import { useI18n } from '@wordpress/react-i18n';
@@ -29,10 +28,7 @@ export default function CreditCardNumberField( {
const { __ } = useI18n();
const { formStatus } = useFormStatus();
const isDisabled = formStatus !== FormStatus.READY;
- const brand: string = useSelect(
- ( select ) => ( select( 'wpcom-credit-card' ) as WpcomCreditCardSelectors ).getBrand(),
- []
- );
+
const { cardNumber: cardNumberError } = useSelect(
( select ) => ( select( 'wpcom-credit-card' ) as WpcomCreditCardSelectors ).getCardDataErrors(),
[]
@@ -71,6 +67,7 @@ export default function CreditCardNumberField( {
options={ {
style: stripeElementStyle,
disabled: isDisabled,
+ showIcon: true,
} }
onReady={ () => {
setIsStripeFullyLoaded( true );
@@ -79,7 +76,6 @@ export default function CreditCardNumberField( {
handleStripeFieldChange( input );
} }
/>
-
{ cardNumberError && { cardNumberError } }
diff --git a/client/my-sites/checkout/src/payment-methods/credit-card/index.tsx b/client/my-sites/checkout/src/payment-methods/credit-card/index.tsx
index 1349f3ef6ed671..a4c43612949b0c 100644
--- a/client/my-sites/checkout/src/payment-methods/credit-card/index.tsx
+++ b/client/my-sites/checkout/src/payment-methods/credit-card/index.tsx
@@ -3,6 +3,7 @@ import { useSelect } from '@wordpress/data';
import { useI18n } from '@wordpress/react-i18n';
import { Fragment } from 'react';
import {
+ CBLogo,
VisaLogo,
MastercardLogo,
AmexLogo,
@@ -42,9 +43,10 @@ function CreditCardSummary() {
);
}
-const CreditCardLabel: React.FC< { hasExistingCardMethods: boolean | undefined } > = ( {
- hasExistingCardMethods,
-} ) => {
+const CreditCardLabel: React.FC< {
+ hasExistingCardMethods: boolean | undefined;
+ currency: string | null;
+} > = ( { hasExistingCardMethods, currency } ) => {
const { __ } = useI18n();
return (
@@ -53,14 +55,15 @@ const CreditCardLabel: React.FC< { hasExistingCardMethods: boolean | undefined }
) : (
{ __( 'Credit or debit card' ) }
) }
-
+
);
};
-function CreditCardLogos() {
+function CreditCardLogos( { currency }: { currency: string | null } ) {
return (
-
+
+ { currency === 'EUR' && }
@@ -69,6 +72,7 @@ function CreditCardLogos() {
}
export function createCreditCardMethod( {
+ currency,
store,
shouldUseEbanx,
shouldShowTaxFields,
@@ -76,6 +80,7 @@ export function createCreditCardMethod( {
allowUseForAllSubscriptions,
hasExistingCardMethods,
}: {
+ currency: string | null;
store: CardStoreType;
shouldUseEbanx?: boolean;
shouldShowTaxFields?: boolean;
@@ -86,7 +91,9 @@ export function createCreditCardMethod( {
return {
id: 'card',
paymentProcessorId: 'card',
- label: ,
+ label: (
+
+ ),
hasRequiredFields: true,
activeContent: (
openPhpMyAdmin( siteId ) }
busy={ ! disabled && loading }
disabled={ disabled }
>
- { translate( 'Open phpMyAdmin' ) }
-
+
+ { translate( 'Open phpMyAdmin' ) }
+
{ ! disabled && (
diff --git a/client/my-sites/hosting/phpmyadmin-card/style.scss b/client/my-sites/hosting/phpmyadmin-card/style.scss
index 4ed42be3824e41..d3dbfb3fc64b6f 100644
--- a/client/my-sites/hosting/phpmyadmin-card/style.scss
+++ b/client/my-sites/hosting/phpmyadmin-card/style.scss
@@ -1,4 +1,7 @@
.phpmyadmin-card button {
+ &.is-primary svg {
+ fill: var(--color-text-inverted);
+ }
svg {
vertical-align: text-bottom;
margin-left: 10px;
diff --git a/client/my-sites/hosting/restore-plan-software-card/index.js b/client/my-sites/hosting/restore-plan-software-card/index.js
index d9e8dce2eb1a01..46f49e227bc368 100644
--- a/client/my-sites/hosting/restore-plan-software-card/index.js
+++ b/client/my-sites/hosting/restore-plan-software-card/index.js
@@ -43,9 +43,7 @@ export default function RestorePlanSoftwareCard() {
'If your website is missing plugins and themes that come with your plan, you may restore them here.'
) }
-
- { translate( "Restore plugins and themes for my website's plan" ) }
-
+ { translate( 'Restore' ) }
);
}
diff --git a/client/my-sites/hosting/sftp-card/index.js b/client/my-sites/hosting/sftp-card/index.js
index dfe46f0adb96dc..0271d551396c19 100644
--- a/client/my-sites/hosting/sftp-card/index.js
+++ b/client/my-sites/hosting/sftp-card/index.js
@@ -289,7 +289,7 @@ export const SftpCard = ( {
}
) }
-
+
{ translate( 'Create credentials' ) }
>
diff --git a/client/my-sites/hosting/staging-site-card/card-content/staging-sync-card.tsx b/client/my-sites/hosting/staging-site-card/card-content/staging-sync-card.tsx
index f19366e40cd91e..5f3af6db89f1f6 100644
--- a/client/my-sites/hosting/staging-site-card/card-content/staging-sync-card.tsx
+++ b/client/my-sites/hosting/staging-site-card/card-content/staging-sync-card.tsx
@@ -228,7 +228,6 @@ const StagingToProductionSync = ( {
-
+
);
next();
diff --git a/client/my-sites/importer/newsletter/content-upload/author-mapping-pane.jsx b/client/my-sites/importer/newsletter/content-upload/author-mapping-pane.jsx
index 1f2a8b34c53ef0..5ef24896c15cff 100644
--- a/client/my-sites/importer/newsletter/content-upload/author-mapping-pane.jsx
+++ b/client/my-sites/importer/newsletter/content-upload/author-mapping-pane.jsx
@@ -157,7 +157,7 @@ class AuthorMappingPane extends PureComponent {
} ) }
-
+
Continue import
diff --git a/client/my-sites/importer/newsletter/content-upload/file-importer.jsx b/client/my-sites/importer/newsletter/content-upload/file-importer.jsx
index ce40157444f317..cbc7114b1fc592 100644
--- a/client/my-sites/importer/newsletter/content-upload/file-importer.jsx
+++ b/client/my-sites/importer/newsletter/content-upload/file-importer.jsx
@@ -44,6 +44,7 @@ class FileImporter extends PureComponent {
icon: PropTypes.string.isRequired,
description: PropTypes.node.isRequired,
uploadDescription: PropTypes.node,
+ acceptedFileTypes: PropTypes.array,
} ).isRequired,
importerStatus: PropTypes.shape( {
errorData: PropTypes.shape( {
@@ -80,7 +81,8 @@ class FileImporter extends PureComponent {
};
render() {
- const { title, overrideDestination, uploadDescription, optionalUrl } = this.props.importerData;
+ const { title, overrideDestination, uploadDescription, optionalUrl, acceptedFileTypes } =
+ this.props.importerData;
const { importerStatus, site, fromSite, nextStepUrl, skipNextStep } = this.props;
const { errorData, importerState, summaryModalOpen } = importerStatus;
const isEnabled = appStates.DISABLED !== importerState;
@@ -153,6 +155,7 @@ class FileImporter extends PureComponent {
site={ site }
optionalUrl={ optionalUrl }
fromSite={ fromSite }
+ acceptedFileTypes={ acceptedFileTypes }
nextStepUrl={ nextStepUrl }
skipNextStep={ skipNextStep }
/>
diff --git a/client/my-sites/importer/newsletter/content-upload/import-summary-modal.tsx b/client/my-sites/importer/newsletter/content-upload/import-summary-modal.tsx
index 36b2970bd1a3c3..ed93969569548c 100644
--- a/client/my-sites/importer/newsletter/content-upload/import-summary-modal.tsx
+++ b/client/my-sites/importer/newsletter/content-upload/import-summary-modal.tsx
@@ -8,6 +8,68 @@ interface ImportSummaryModalProps {
authorsNumber?: number;
}
+function getContent( postsNumber: number, pagesNumber: number, attachmentsNumber: number ) {
+ if ( postsNumber > 0 && pagesNumber > 0 && attachmentsNumber > 0 ) {
+ return (
+
+ We’ve found { postsNumber } posts , { pagesNumber } pages
+ and { attachmentsNumber } media to import.
+
+ );
+ }
+
+ if ( postsNumber > 0 && pagesNumber > 0 ) {
+ return (
+
+ We’ve found { postsNumber } posts and{ ' ' }
+ { pagesNumber } pages to import.
+
+ );
+ }
+
+ if ( postsNumber > 0 && attachmentsNumber > 0 ) {
+ return (
+
+ We’ve found { postsNumber } posts and{ ' ' }
+ { attachmentsNumber } media to import.
+
+ );
+ }
+
+ if ( pagesNumber > 0 && attachmentsNumber > 0 ) {
+ return (
+
+ We’ve found { pagesNumber } pages and{ ' ' }
+ { attachmentsNumber } media to import.
+
+ );
+ }
+
+ if ( postsNumber > 0 ) {
+ return (
+
+ We’ve found { postsNumber } posts to import.
+
+ );
+ }
+
+ if ( pagesNumber > 0 ) {
+ return (
+
+ We’ve found { pagesNumber } pages to import.
+
+ );
+ }
+
+ if ( attachmentsNumber > 0 ) {
+ return (
+
+ We’ve found { attachmentsNumber } media to import.
+
+ );
+ }
+}
+
export default function ImportSummaryModal( {
onRequestClose,
postsNumber,
@@ -17,17 +79,13 @@ export default function ImportSummaryModal( {
}: ImportSummaryModalProps ) {
return (
-
- We’ve found { postsNumber } posts , { pagesNumber } pages { ' ' }
- and { attachmentsNumber } media to import.
- { authorsNumber && (
- <>
-
- Your Substack publication has { authorsNumber } authors . Next, you can
- match them with existing site users.
- >
- ) }
-
+ { getContent( postsNumber, pagesNumber, attachmentsNumber ) }
+ { authorsNumber && (
+
+ Your Substack publication has { authorsNumber } authors . Next, you can
+ match them with existing site users.
+
+ ) }
Continue
diff --git a/client/my-sites/importer/newsletter/content-upload/uploading-pane.jsx b/client/my-sites/importer/newsletter/content-upload/uploading-pane.jsx
index 26394ee74adcb1..705b287f7ec826 100644
--- a/client/my-sites/importer/newsletter/content-upload/uploading-pane.jsx
+++ b/client/my-sites/importer/newsletter/content-upload/uploading-pane.jsx
@@ -1,4 +1,6 @@
-import { ProgressBar, FormInputValidation, FormLabel, Gridicon } from '@automattic/components';
+import { FormInputValidation, FormLabel } from '@automattic/components';
+import { ProgressBar } from '@wordpress/components';
+import { Icon, cloudUpload } from '@wordpress/icons';
import clsx from 'clsx';
import { localize } from 'i18n-calypso';
import { truncate } from 'lodash';
@@ -48,6 +50,7 @@ export class UploadingPane extends PureComponent {
validate: PropTypes.func,
} ),
fromSite: PropTypes.string,
+ acceptedFileTypes: PropTypes.array,
nextStepUrl: PropTypes.string,
skipNextStep: PropTypes.func,
};
@@ -106,9 +109,7 @@ export class UploadingPane extends PureComponent {
case appStates.UPLOAD_PROCESSING:
case appStates.UPLOADING: {
const uploadPercent = percentComplete;
- const progressClasses = clsx( 'importer__upload-progress', {
- 'is-complete': uploadPercent > 95,
- } );
+
const uploaderPrompt =
importerState === appStates.UPLOADING && uploadPercent < 99
? this.props.translate( 'Uploading %(filename)s\u2026', {
@@ -117,14 +118,9 @@ export class UploadingPane extends PureComponent {
: this.props.translate( 'Processing uploaded file\u2026' );
return (
-
+
{ uploaderPrompt }
-
99 || importerState === appStates.UPLOAD_PROCESSING }
- />
+
);
}
@@ -237,7 +233,7 @@ export class UploadingPane extends PureComponent {
};
render() {
- const { importerStatus, fromSite, nextStepUrl, skipNextStep } = this.props;
+ const { importerStatus, fromSite, acceptedFileTypes, nextStepUrl, skipNextStep } = this.props;
const isReadyForImport = this.isReadyForImport();
const importerStatusClasses = clsx(
'importer__upload-content',
@@ -260,13 +256,7 @@ export class UploadingPane extends PureComponent {
onKeyPress={ isReadyForImport ? this.handleKeyPress : null }
>
-
+
{ this.getMessage() }
{ isReadyForImport && (
@@ -275,6 +265,7 @@ export class UploadingPane extends PureComponent {
type="file"
name="exportFile"
onChange={ this.initiateFromForm }
+ accept={ acceptedFileTypes?.length ? acceptedFileTypes.join( ',' ) : undefined }
/>
) }
diff --git a/client/my-sites/importer/newsletter/content.tsx b/client/my-sites/importer/newsletter/content.tsx
index 3164e6bee06744..d496699853a3da 100644
--- a/client/my-sites/importer/newsletter/content.tsx
+++ b/client/my-sites/importer/newsletter/content.tsx
@@ -1,5 +1,6 @@
-import { Card, Button, Gridicon } from '@automattic/components';
-import { QueryArgParsed } from '@wordpress/url/build-types/get-query-arg';
+import { Card } from '@automattic/components';
+import { Button } from '@wordpress/components';
+import { external } from '@wordpress/icons';
import { useEffect } from 'react';
import importerConfig from 'calypso/lib/importer/importer-config';
import { EVERY_FIVE_SECONDS, Interval } from 'calypso/lib/interval';
@@ -12,10 +13,9 @@ import type { SiteDetails } from '@automattic/data-stores';
type ContentProps = {
nextStepUrl: string;
- selectedSite?: SiteDetails;
+ selectedSite: SiteDetails;
siteSlug: string;
- fromSite: QueryArgParsed;
- content: any;
+ fromSite: string;
skipNextStep: () => void;
};
@@ -26,8 +26,8 @@ export default function Content( {
fromSite,
skipNextStep,
}: ContentProps ) {
- const siteTitle = selectedSite?.title;
- const siteId = selectedSite?.ID;
+ const siteTitle = selectedSite.title;
+ const siteId = selectedSite.ID;
const siteImports = useSelector( ( state ) => getImporterStatusForSiteId( state, siteId ) );
@@ -40,10 +40,6 @@ export default function Content( {
useEffect( fetchImporters, [ siteId, dispatch ] );
useEffect( startImporting, [ siteId, dispatch, siteImports ] );
- if ( ! selectedSite ) {
- return null;
- }
-
function startImporting() {
siteId && siteImports.length === 0 && dispatch( startImport( siteId ) );
}
@@ -78,8 +74,10 @@ export default function Content( {
href={ `https://${ fromSite }/publish/settings?search=export` }
target="_blank"
rel="noreferrer noopener"
+ icon={ external }
+ variant="secondary"
>
- Export content
+ Export content
Step 2: Import your content to WordPress.com
@@ -90,7 +88,7 @@ export default function Content( {
site={ selectedSite }
importerStatus={ importerStatus }
importerData={ importerData }
- fromSite={ fromSite as string }
+ fromSite={ fromSite }
nextStepUrl={ nextStepUrl }
skipNextStep={ skipNextStep }
/>
diff --git a/client/my-sites/importer/newsletter/importer.scss b/client/my-sites/importer/newsletter/importer.scss
index 15a3430b934eab..0115a20b2de7b7 100644
--- a/client/my-sites/importer/newsletter/importer.scss
+++ b/client/my-sites/importer/newsletter/importer.scss
@@ -40,47 +40,62 @@
box-shadow: none;
}
- .summary__content p {
- display: flex;
- gap: 8px;
+
+ .summary__content input[type="checkbox"] {
+ margin-right: 8px;
+ }
+
+ .summary__content svg {
+ float: left;
+ margin-right: 6px;
+ margin-top: -1px;
+ fill: var(--studio-gray-50);
}
.stripe-logo {
width: 48px;
padding-left: 8px;
}
-}
-.select-newsletter-form__help {
- margin-top: 8px;
- font-size: 0.75rem;
+ .select-newsletter-form__help {
+ margin-top: 8px;
+ margin-bottom: 0;
+ font-size: 0.75rem;
- &.is-error {
- color: var(--color-error);
+ &.is-error {
+ color: var(--color-error);
+ }
}
}
-
+.content-upload-form__in-progress,
.subscriber-upload-form__in-progress,
.subscriber-upload-form .file-picker {
- background: #f6f7f7;
- height: 180px;
width: 100%;
- border: 1px dashed var(--studio-gray-50);
- border-radius: 4px;
display: flex;
flex-direction: column;
margin-bottom: 16px;
align-items: center;
- padding-top: 50px;
color: var(--studio-gray-50);
}
+
+.subscriber-upload-form__dropzone {
+ background: #f6f7f7;
+ padding-top: 50px;
+ cursor: pointer;
+ position: relative;
+ height: 180px;
+ border: 1px dashed var(--studio-gray-50);
+ border-radius: 4px;
+ margin-bottom: 20px;
+}
+
.subscriber-upload-form__in-progress svg,
.subscriber-upload-form .file-picker svg {
width: 48px;
height: 48px;
text-align: center;
padding: 8px;
- fill: var(--studio-gray-50);
+ fill: var(--studio-gray-20);
}
.subscriber-upload-form__modal ul {
@@ -88,13 +103,21 @@
padding: 0;
list-style: none;
}
+
.subscriber-upload-form__modal li {
display: flex;
}
+
+.subscriber-upload-form__modal strong {
+ margin-right: 8px;
+}
+
.subscriber-upload-form__modal svg {
fill: var(--studio-gray-50);
margin-right: 8px;
}
+
.select-newsletter-form .is-loading {
@include placeholder( --color-neutral-10 );
+ margin-bottom: 0;
}
diff --git a/client/my-sites/importer/newsletter/importer.tsx b/client/my-sites/importer/newsletter/importer.tsx
index 6e928ba3d1ac01..5a3a9bd18dfb9a 100644
--- a/client/my-sites/importer/newsletter/importer.tsx
+++ b/client/my-sites/importer/newsletter/importer.tsx
@@ -1,3 +1,4 @@
+import page from '@automattic/calypso-router';
import { Spinner } from '@wordpress/components';
import { Icon, check } from '@wordpress/icons';
import { addQueryArgs, getQueryArg } from '@wordpress/url';
@@ -5,7 +6,10 @@ import { useState, useEffect } from 'react';
import { UrlData } from 'calypso/blocks/import/types';
import FormattedHeader from 'calypso/components/formatted-header';
import StepProgress, { ClickHandler } from 'calypso/components/step-progress';
-import { usePaidNewsletterQuery } from 'calypso/data/paid-newsletter/use-paid-newsletter-query';
+import {
+ StepId,
+ usePaidNewsletterQuery,
+} from 'calypso/data/paid-newsletter/use-paid-newsletter-query';
import { useResetMutation } from 'calypso/data/paid-newsletter/use-reset-mutation';
import { useSkipNextStepMutation } from 'calypso/data/paid-newsletter/use-skip-next-step-mutation';
import { useAnalyzeUrlQuery } from 'calypso/data/site-profiler/use-analyze-url-query';
@@ -22,9 +26,7 @@ import './importer.scss';
const steps = [ Content, Subscribers, PaidSubscribers, Summary ];
-const stepSlugs = [ 'content', 'subscribers', 'paid-subscribers', 'summary' ];
-
-const noop = () => {};
+const stepSlugs: StepId[] = [ 'content', 'subscribers', 'paid-subscribers', 'summary' ];
const logoChainLogos = [
{ name: 'substack', color: 'var(--color-substack)' },
@@ -34,46 +36,91 @@ const logoChainLogos = [
type NewsletterImporterProps = {
siteSlug: string;
engine: string;
- step: string;
+ step?: StepId;
};
function getTitle( urlData?: UrlData ) {
if ( urlData?.meta?.title ) {
- return `Import ${ urlData?.meta?.title }`;
+ return `Import ${ urlData.meta.title }`;
}
return 'Import your newsletter';
}
-export default function NewsletterImporter( { siteSlug, engine, step }: NewsletterImporterProps ) {
+export default function NewsletterImporter( {
+ siteSlug,
+ engine,
+ step = 'content',
+}: NewsletterImporterProps ) {
+ const fromSite = getQueryArg( window.location.href, 'from' ) as string;
const selectedSite = useSelector( getSelectedSite ) ?? undefined;
const [ validFromSite, setValidFromSite ] = useState( false );
+ const [ autoFetchData, setAutoFetchData ] = useState( false );
const stepsProgress: ClickHandler[] = [
- { message: 'Content', onClick: noop },
- { message: 'Subscribers', onClick: noop },
- { message: 'Paid Subscribers', onClick: noop },
- { message: 'Summary', onClick: noop },
+ {
+ message: 'Content',
+ onClick: () => {
+ page(
+ addQueryArgs( `/import/newsletter/${ engine }/${ siteSlug }/content`, {
+ from: fromSite,
+ } )
+ );
+ },
+ show: 'onComplete',
+ },
+ {
+ message: 'Subscribers',
+ onClick: () => {
+ page(
+ addQueryArgs( `/import/newsletter/${ engine }/${ siteSlug }/subscribers`, {
+ from: fromSite,
+ } )
+ );
+ },
+ show: 'onComplete',
+ },
+ {
+ message: 'Paid Subscribers',
+ onClick: () => {
+ page(
+ addQueryArgs( `/import/newsletter/${ engine }/${ siteSlug }/paid-subscribers`, {
+ from: fromSite,
+ } )
+ );
+ },
+ show: 'onComplete',
+ },
+ { message: 'Summary', onClick: () => {} },
];
- let fromSite = getQueryArg( window.location.href, 'from' ) as string | string[];
-
// Steps
- fromSite = Array.isArray( fromSite ) ? fromSite[ 0 ] : fromSite;
- if ( fromSite && ! step ) {
- step = stepSlugs[ 0 ];
- }
-
let stepIndex = 0;
let nextStep = stepSlugs[ 0 ];
const { data: paidNewsletterData, isFetching: isFetchingPaidNewsletter } = usePaidNewsletterQuery(
engine,
step,
- selectedSite?.ID
+ selectedSite?.ID,
+ autoFetchData
);
+ useEffect( () => {
+ if (
+ paidNewsletterData?.steps?.content?.status === 'importing' ||
+ paidNewsletterData?.steps.subscribers?.status === 'importing'
+ ) {
+ setAutoFetchData( true );
+ } else {
+ setAutoFetchData( false );
+ }
+ }, [
+ paidNewsletterData?.steps?.content?.status,
+ paidNewsletterData?.steps.subscribers?.status,
+ setAutoFetchData,
+ ] );
+
stepSlugs.forEach( ( stepName, index ) => {
if ( stepName === step ) {
stepIndex = index;
@@ -85,7 +132,7 @@ export default function NewsletterImporter( { siteSlug, engine, step }: Newslett
if ( status === 'done' ) {
stepsProgress[ index ].indicator =
;
}
- if ( status === 'processing' ) {
+ if ( status === 'importing' ) {
stepsProgress[ index ].indicator =
;
}
}
@@ -98,7 +145,12 @@ export default function NewsletterImporter( { siteSlug, engine, step }: Newslett
let stepContent = {};
if ( paidNewsletterData?.steps ) {
- stepContent = paidNewsletterData?.steps[ step ]?.content ?? {};
+ // This is useful for the summary step.
+ if ( ! paidNewsletterData?.steps[ step ] ) {
+ stepContent = paidNewsletterData.steps;
+ } else {
+ stepContent = paidNewsletterData.steps[ step ]?.content ?? {};
+ }
}
useEffect( () => {
@@ -128,7 +180,6 @@ export default function NewsletterImporter( { siteSlug, engine, step }: Newslett
stepUrl={ stepUrl }
urlData={ urlData }
isLoading={ isFetching || isResetPaidNewsletterPending }
- validFromSite={ validFromSite }
/>
) }
@@ -145,10 +196,12 @@ export default function NewsletterImporter( { siteSlug, engine, step }: Newslett
skipNextStep={ () => {
skipNextStep( selectedSite.ID, engine, nextStep, step );
} }
+ // FIXME
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-expect-error
cardData={ stepContent }
engine={ engine }
isFetchingContent={ isFetchingPaidNewsletter }
- content={ stepContent }
/>
) }
diff --git a/client/my-sites/importer/newsletter/paid-subscribers.tsx b/client/my-sites/importer/newsletter/paid-subscribers.tsx
index 1bb189f8b75fe1..4138b56b627149 100644
--- a/client/my-sites/importer/newsletter/paid-subscribers.tsx
+++ b/client/my-sites/importer/newsletter/paid-subscribers.tsx
@@ -1,16 +1,19 @@
+import { SiteDetails } from '@automattic/data-stores';
import { hasQueryArg } from '@wordpress/url';
import { useEffect } from 'react';
+import { PaidSubscribersStepContent } from 'calypso/data/paid-newsletter/use-paid-newsletter-query';
import { useDispatch } from 'calypso/state';
import { infoNotice, successNotice } from 'calypso/state/notices/actions';
import ConnectStripe from './paid-subscribers/connect-stripe';
import MapPlans from './paid-subscribers/map-plans';
+
type Props = {
nextStepUrl: string;
skipNextStep: () => void;
fromSite: string;
engine: string;
- cardData: any;
- selectedSite: any;
+ cardData: PaidSubscribersStepContent;
+ selectedSite: SiteDetails;
isFetchingContent: boolean;
};
@@ -54,7 +57,7 @@ export default function PaidSubscribers( {
cardData={ cardData }
skipNextStep={ skipNextStep }
engine={ engine }
- siteId={ selectedSite?.ID }
+ siteId={ selectedSite.ID }
currentStep="paid-subscribers"
/>
) }
diff --git a/client/my-sites/importer/newsletter/paid-subscribers/connect-stripe.tsx b/client/my-sites/importer/newsletter/paid-subscribers/connect-stripe.tsx
index 483fdd1670a613..2f3e0f160bd0ca 100644
--- a/client/my-sites/importer/newsletter/paid-subscribers/connect-stripe.tsx
+++ b/client/my-sites/importer/newsletter/paid-subscribers/connect-stripe.tsx
@@ -1,6 +1,7 @@
import { Card } from '@automattic/components';
import { getQueryArg, addQueryArgs } from '@wordpress/url';
import StripeLogo from 'calypso/assets/images/jetpack/stripe-logo-white.svg';
+import { PaidSubscribersStepContent } from 'calypso/data/paid-newsletter/use-paid-newsletter-query';
import { recordTracksEvent } from 'calypso/state/analytics/actions';
import ImporterActionButton from '../../importer-action-buttons/action-button';
import ImporterActionButtonContainer from '../../importer-action-buttons/container';
@@ -25,7 +26,7 @@ function updateConnectUrl( connectUrl: string, fromSite: string, engine: string
type Props = {
nextStepUrl: string;
skipNextStep: () => void;
- cardData: any;
+ cardData: PaidSubscribersStepContent;
fromSite: string;
engine: string;
isFetchingContent: boolean;
diff --git a/client/my-sites/importer/newsletter/paid-subscribers/map-plan.scss b/client/my-sites/importer/newsletter/paid-subscribers/map-plan.scss
index 6765f64bd1fe13..2c9e241aa46639 100644
--- a/client/my-sites/importer/newsletter/paid-subscribers/map-plan.scss
+++ b/client/my-sites/importer/newsletter/paid-subscribers/map-plan.scss
@@ -12,10 +12,11 @@
.map-plan__info {
min-height: 80px;
display: flex;
- align-items: center;
min-width: 230px;
+ align-items: center;
}
.map-plan__info {
+ align-items: start;
flex-direction: column;
justify-content: center;
p {
@@ -30,7 +31,7 @@
.map-plan__select-product .map-plan__selected {
display: flex;
height: auto;
-
+ text-align: right;
p {
margin: 0;
}
diff --git a/client/my-sites/importer/newsletter/paid-subscribers/map-plan.tsx b/client/my-sites/importer/newsletter/paid-subscribers/map-plan.tsx
index 07e0b05e997af5..54e2d86eeafd38 100644
--- a/client/my-sites/importer/newsletter/paid-subscribers/map-plan.tsx
+++ b/client/my-sites/importer/newsletter/paid-subscribers/map-plan.tsx
@@ -4,28 +4,10 @@ import { Fragment } from '@wordpress/element';
import { chevronDown, Icon, arrowRight } from '@wordpress/icons';
import { useState, KeyboardEvent } from 'react';
import { useMapStripePlanToProductMutation } from 'calypso/data/paid-newsletter/use-map-stripe-plan-to-product-mutation';
+import { Product, Plan } from 'calypso/data/paid-newsletter/use-paid-newsletter-query';
import './map-plan.scss';
-type Plan = {
- plan_id: string;
- name: string;
- plan_interval: string;
- active_subscriptions: number;
- is_active: boolean;
- plan_currency: string;
- plan_amount_decimal: number;
- product_id: string;
-};
-
-type Product = {
- id: number;
- price: string;
- currency: string;
- title: string;
- interval: string;
-};
-
export type TierToAdd = {
currency: string;
price: number;
@@ -42,16 +24,16 @@ export type TierToAdd = {
type MapPlanProps = {
plan: Plan;
- products: Array< Product >;
- map_plans: any;
- siteId: string;
+ products: Product[];
+ map_plans: Record< string, string >;
+ siteId: number;
engine: string;
currentStep: string;
onProductAdd: ( arg0: TierToAdd | null ) => void;
tierToAdd: TierToAdd;
};
-function displayProduct( product: Product | undefined ) {
+function displayProduct( product?: Product ) {
if ( ! product ) {
return 'Select a Newsletter Tier';
}
@@ -66,7 +48,7 @@ function displayProduct( product: Product | undefined ) {
);
}
-function getProductChoices( products: Array< Product > ) {
+function getProductChoices( products: Product[] ) {
return products.map( ( product ) => ( {
info: `${ formatCurrency( parseFloat( product.price ), product.currency ) } / ${
product.interval
diff --git a/client/my-sites/importer/newsletter/paid-subscribers/map-plans.tsx b/client/my-sites/importer/newsletter/paid-subscribers/map-plans.tsx
index 6aa1b7c1fe43cd..067d64456e9a0f 100644
--- a/client/my-sites/importer/newsletter/paid-subscribers/map-plans.tsx
+++ b/client/my-sites/importer/newsletter/paid-subscribers/map-plans.tsx
@@ -2,6 +2,7 @@ import { Card } from '@automattic/components';
import { formatCurrency } from '@automattic/format-currency';
import { useQueryClient } from '@tanstack/react-query';
import { useEffect, useState, useRef } from 'react';
+import { PaidSubscribersStepContent } from 'calypso/data/paid-newsletter/use-paid-newsletter-query';
import RecurringPaymentsPlanAddEditModal from 'calypso/my-sites/earn/components/add-edit-plan-modal';
import {
PLAN_YEARLY_FREQUENCY,
@@ -18,8 +19,8 @@ import { MapPlan, TierToAdd } from './map-plan';
type Props = {
nextStepUrl: string;
skipNextStep: () => void;
- cardData: any;
- siteId: string;
+ cardData: PaidSubscribersStepContent;
+ siteId: number;
engine: string;
currentStep: string;
};
@@ -58,13 +59,17 @@ export default function MapPlans( {
}
sizeOfProductsRef.current = sizeOfProducts;
queryClient.invalidateQueries( {
- queryKey: [ 'paid-newsletter-importer', siteId, engine, currentStep ],
+ queryKey: [ 'paid-newsletter-importer', siteId, engine ],
} );
}, [ sizeOfProducts, sizeOfProductsRef, siteId, engine, currentStep, queryClient ] );
- const monthyPlan = cardData.plans.find( ( plan: any ) => plan.plan_interval === 'month' );
+ const monthyPlan = cardData.plans.find( ( plan ) => plan.plan_interval === 'month' );
+ const annualPlan = cardData.plans.find( ( plan ) => plan.plan_interval === 'year' );
- const annualPlan = cardData.plans.find( ( plan: any ) => plan.plan_interval === 'year' );
+ // TODO what if those plans are undefined?
+ if ( ! monthyPlan || ! annualPlan ) {
+ return;
+ }
const tierToAdd = {
currency: monthyPlan.plan_currency,
diff --git a/client/my-sites/importer/newsletter/select-newsletter-form.tsx b/client/my-sites/importer/newsletter/select-newsletter-form.tsx
index 5fea47165b89a8..0cbb34da198074 100644
--- a/client/my-sites/importer/newsletter/select-newsletter-form.tsx
+++ b/client/my-sites/importer/newsletter/select-newsletter-form.tsx
@@ -10,16 +10,10 @@ type Props = {
stepUrl: string;
urlData?: UrlData;
isLoading: boolean;
- validFromSite: boolean;
};
-export default function SelectNewsletterForm( {
- stepUrl,
- urlData,
- isLoading,
- validFromSite,
-}: Props ) {
- const [ hasError, setHasError ] = useState( ! validFromSite );
+export default function SelectNewsletterForm( { stepUrl, urlData, isLoading }: Props ) {
+ const [ hasError, setHasError ] = useState( false );
const handleAction = ( fromSite: string ) => {
if ( ! isValidUrl( fromSite ) ) {
@@ -34,35 +28,29 @@ export default function SelectNewsletterForm( {
if ( isLoading ) {
return (
-
-
+
+
);
}
return (
-
-
-
- { hasError && (
-
- Please enter a valid substack URL.
-
- ) }
- { ! hasError && (
-
- Enter the URL of the substack newsletter that you wish to import.
-
- ) }
-
+
+
+ { hasError && (
+ Please enter a valid Substack URL.
+ ) }
+ { ! hasError && (
+
+ Enter the URL of the Substack newsletter that you wish to import.
+
+ ) }
);
}
diff --git a/client/my-sites/importer/newsletter/subscriber-upload-form.tsx b/client/my-sites/importer/newsletter/subscriber-upload-form.tsx
index e74e41a2a1bb12..a5b07681322fca 100644
--- a/client/my-sites/importer/newsletter/subscriber-upload-form.tsx
+++ b/client/my-sites/importer/newsletter/subscriber-upload-form.tsx
@@ -1,11 +1,10 @@
import { FormInputValidation } from '@automattic/components';
import { Subscriber } from '@automattic/data-stores';
import { localizeUrl } from '@automattic/i18n-utils';
-import { useActiveJobRecognition } from '@automattic/subscriber';
-import { Button, ProgressBar, Modal } from '@wordpress/components';
+import { ProgressBar } from '@wordpress/components';
import { useSelect, useDispatch } from '@wordpress/data';
-import { Icon, cloudUpload, people, currencyEuro } from '@wordpress/icons';
-import { useCallback, useState, useEffect, useRef, FormEvent } from 'react';
+import { Icon, cloudUpload } from '@wordpress/icons';
+import { useCallback, useState, FormEvent } from 'react';
import DropZone from 'calypso/components/drop-zone';
import FilePicker from 'calypso/components/file-picker';
import { recordTracksEvent } from 'calypso/state/analytics/actions';
@@ -16,13 +15,13 @@ type Props = {
nextStepUrl: string;
siteId: number;
skipNextStep: () => void;
+ cardData: any;
};
export default function SubscriberUploadForm( { nextStepUrl, siteId, skipNextStep }: Props ) {
const [ selectedFile, setSelectedFile ] = useState< File >();
- const [ isOpen, setIsOpen ] = useState( false );
- const { importCsvSubscribers, getSubscribersImports } = useDispatch( Subscriber.store );
+ const { importCsvSubscribers } = useDispatch( Subscriber.store );
const { importSelector } = useSelect( ( select ) => {
const subscriber = select( Subscriber.store );
@@ -32,13 +31,11 @@ export default function SubscriberUploadForm( { nextStepUrl, siteId, skipNextSte
};
}, [] );
- const prevInProgress = useRef( importSelector?.inProgress );
-
const [ isSelectedFileValid, setIsSelectedFileValid ] = useState( false );
const onSubmit = useCallback(
async ( event: FormEvent< HTMLFormElement > ) => {
event.preventDefault();
- selectedFile && importCsvSubscribers( siteId, selectedFile );
+ selectedFile && importCsvSubscribers( siteId, selectedFile, [], true );
},
[ siteId, selectedFile ]
);
@@ -59,62 +56,18 @@ export default function SubscriberUploadForm( { nextStepUrl, siteId, skipNextSte
return validExtensions.includes( match?.groups?.extension.toLowerCase() as string );
}
- useActiveJobRecognition( siteId );
-
- useEffect( () => {
- getSubscribersImports( siteId );
- }, [ siteId, getSubscribersImports ] );
-
- useEffect( () => {
- if ( prevInProgress.current ) {
- setIsOpen( true );
- }
- prevInProgress.current = importSelector?.inProgress;
- }, [ importSelector?.inProgress ] );
-
const importSubscribersUrl =
'https://wordpress.com/support/launch-a-newsletter/import-subscribers-to-a-newsletter/';
if ( importSelector?.inProgress ) {
return (
-
- );
- }
-
- if ( isOpen ) {
- return (
- setIsOpen( false ) }
- className="subscriber-upload-form__modal"
- size="medium"
- >
-
- We’ve found 100 subscribers, where:
-
-
-
- 82 are free subscribers
-
-
-
- 1 have a complimentary
-
-
-
- subscription 18 are paying subscribers
-
-
+
);
}
@@ -125,17 +78,19 @@ export default function SubscriberUploadForm( { nextStepUrl, siteId, skipNextSte
Sorry, you can only upload CSV files. Please try again with a valid file.
) }
-
-
-
- { ! selectedFile && Drag a file, or click to upload a file.
}
- { selectedFile && (
-
- To replace this { selectedFile?.name }
- drag a file, or click to upload different one.
-
- ) }
-
+
+
+
+
+ { ! selectedFile && Drag a file, or click to upload a file.
}
+ { selectedFile && (
+
+ To replace this { selectedFile?.name }
+ drag a file, or click to upload different one.
+
+ ) }
+
+
{ isSelectedFileValid && selectedFile && (
By clicking "Continue," you represent that you've obtained the appropriate consent to
diff --git a/client/my-sites/importer/newsletter/subscribers.tsx b/client/my-sites/importer/newsletter/subscribers.tsx
index c1f41b4c4b6ce6..2c90cfb56f2b8c 100644
--- a/client/my-sites/importer/newsletter/subscribers.tsx
+++ b/client/my-sites/importer/newsletter/subscribers.tsx
@@ -1,47 +1,120 @@
-import { Button, Card, Gridicon } from '@automattic/components';
+import { Card } from '@automattic/components';
+import { Subscriber } from '@automattic/data-stores';
+import { useQueryClient } from '@tanstack/react-query';
+import { Modal, Button } from '@wordpress/components';
+import { useSelect } from '@wordpress/data';
+import { Icon, people, currencyDollar, external } from '@wordpress/icons';
import { QueryArgParsed } from '@wordpress/url/build-types/get-query-arg';
+import { toInteger } from 'lodash';
+import { useEffect, useRef } from 'react';
+import { SubscribersStepContent } from 'calypso/data/paid-newsletter/use-paid-newsletter-query';
import SubscriberUploadForm from './subscriber-upload-form';
import type { SiteDetails } from '@automattic/data-stores';
type Props = {
nextStepUrl: string;
- selectedSite?: SiteDetails;
+ selectedSite: SiteDetails;
fromSite: QueryArgParsed;
skipNextStep: () => void;
+ cardData: SubscribersStepContent;
+ siteSlug: string;
+ engine: string;
};
export default function Subscribers( {
nextStepUrl,
selectedSite,
fromSite,
+ siteSlug,
skipNextStep,
+ cardData,
+ engine,
}: Props ) {
- if ( ! selectedSite ) {
- return null;
- }
+ const queryClient = useQueryClient();
+ const { importSelector } = useSelect( ( select ) => {
+ const subscriber = select( Subscriber.store );
+ return {
+ importSelector: subscriber.getImportSubscribersSelector(),
+ };
+ }, [] );
+
+ const prevInProgress = useRef( importSelector?.inProgress );
+ useEffect( () => {
+ if ( ! prevInProgress.current && importSelector?.inProgress ) {
+ setTimeout( () => {
+ queryClient.invalidateQueries( {
+ queryKey: [ 'paid-newsletter-importer', selectedSite.ID, engine ],
+ } );
+ }, 1500 ); // 1500ms = 1.5s delay so that we have enought time to propagate the changes.
+ }
+
+ prevInProgress.current = importSelector?.inProgress;
+ }, [ importSelector?.inProgress ] );
+
+ const open = cardData?.meta?.status === 'pending' || false;
+
+ const all_emails = toInteger( cardData?.meta?.email_count ) || 0;
+ const paid_emails = toInteger( cardData?.meta?.paid_subscribers_count ) || 0;
+ const free_emails = all_emails - paid_emails;
+
return (
-
- Step 1: Export your subscribers from Substack
-
- To generate a CSV file of all your Substack subscribers, go to the Subscribers tab and click
- 'Export.' Once the CSV file is downloaded, upload it in the next step.
-
-
- Export subscribers
-
-
- Step 2: Import your subscribers to WordPress.com
- { selectedSite.ID && (
-
+ <>
+
+ Step 1: Export your subscribers from Substack
+
+ To generate a CSV file of all your Substack subscribers, go to the Subscribers tab and
+ click 'Export.' Once the CSV file is downloaded, upload it in the next step.
+
+
+ Export subscribers
+
+
+ Step 2: Import your subscribers to WordPress.com
+ { selectedSite.ID && (
+
+ ) }
+
+ { open && (
+ {} }
+ className="subscriber-upload-form__modal"
+ size="medium"
+ >
+
+ We’ve found { all_emails } subscribers.
+
+ { free_emails !== 0 && (
+
+
+ { free_emails } are free subscribers
+
+ ) }
+ { paid_emails !== 0 && (
+
+
+ { paid_emails } are paying subscribers
+
+ ) }
+
+
+
+ Continue
+
+
) }
-
+ >
);
}
diff --git a/client/my-sites/importer/newsletter/summary.tsx b/client/my-sites/importer/newsletter/summary.tsx
index 96eac0f230f6a1..7cce37b3103486 100644
--- a/client/my-sites/importer/newsletter/summary.tsx
+++ b/client/my-sites/importer/newsletter/summary.tsx
@@ -1,28 +1,44 @@
import { Card, ConfettiAnimation } from '@automattic/components';
-import { Icon, post, people, currencyDollar } from '@wordpress/icons';
+import ContentSummary from './summary/content';
+import SubscribersSummary from './summary/subscribers';
+import type { SiteDetails } from '@automattic/data-stores';
-export default function Summary() {
+type Props = {
+ cardData: any;
+ selectedSite: SiteDetails;
+};
+
+export default function Summary( { cardData, selectedSite }: Props ) {
const prefersReducedMotion = window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches;
+ function shouldRenderConfetti( contentStatus: string, subscriberStatue: string ) {
+ if ( contentStatus === 'done' && subscriberStatue === 'done' ) {
+ return true;
+ }
+ if ( contentStatus === 'done' && subscriberStatue === 'skipped' ) {
+ return true;
+ }
+
+ if ( contentStatus === 'skipped' && subscriberStatue === 'done' ) {
+ return true;
+ }
+
+ return false;
+ }
return (
-
- Success!
-
-
Here's an overview of what you'll migrate:
-
-
- 47 posts
-
-
-
- 99 subscribers
-
-
-
- 17 paid subscribers
-
-
+ { shouldRenderConfetti( cardData.content.status, cardData.subscribers.status ) && (
+ <>
+ Success! 🎉
+ >
+ ) }
+
+
);
}
diff --git a/client/my-sites/importer/newsletter/summary/content.tsx b/client/my-sites/importer/newsletter/summary/content.tsx
new file mode 100644
index 00000000000000..b7faefb2aa18e4
--- /dev/null
+++ b/client/my-sites/importer/newsletter/summary/content.tsx
@@ -0,0 +1,67 @@
+import { Icon, post, media, comment, page } from '@wordpress/icons';
+
+type Props = {
+ cardData: any;
+ status: string;
+};
+
+export default function ContentSummary( { status, cardData }: Props ) {
+ if ( status === 'skipped' ) {
+ return (
+
+
+ Content importing was skipped!
+
+
+ );
+ }
+
+ if ( status === 'done' ) {
+ const progress = cardData.progress;
+ return (
+
+
We imported:
+
+ { progress.post.completed !== 0 && (
+
+
+ { progress.post.completed } posts
+
+ ) }
+
+ { progress.page.completed !== 0 && (
+
+
+ { progress.page.completed } pages
+
+ ) }
+
+ { progress.attachment.completed !== 0 && (
+
+
+ { progress.attachment.completed } media
+
+ ) }
+
+ { progress.comment.completed !== 0 && (
+
+
+ { progress.comment.completed } comments
+
+ ) }
+
+ );
+ }
+
+ if ( status === 'importing' || status === 'processing' ) {
+ return (
+
+
+ Content is importing...
+
+
+ );
+ }
+
+ return;
+}
diff --git a/client/my-sites/importer/newsletter/summary/subscribers.tsx b/client/my-sites/importer/newsletter/summary/subscribers.tsx
new file mode 100644
index 00000000000000..12d2c806a39e01
--- /dev/null
+++ b/client/my-sites/importer/newsletter/summary/subscribers.tsx
@@ -0,0 +1,113 @@
+import { FormLabel } from '@automattic/components';
+import { Button } from '@wordpress/components';
+import { useState } from '@wordpress/element';
+import { Icon, people, currencyDollar, atSymbol } from '@wordpress/icons';
+import { ChangeEvent } from 'react';
+import FormCheckbox from 'calypso/components/forms/form-checkbox';
+import { useSubscriberImportMutation } from 'calypso/data/paid-newsletter/use-subscriber-import-mutation';
+
+type Props = {
+ cardData: any;
+ status: string;
+ proStatus: string;
+ siteId: number;
+};
+export default function SubscriberSummary( { status, proStatus, cardData, siteId }: Props ) {
+ const paidSubscribers = cardData?.meta?.paid_subscribers_count ?? 0;
+ const hasPaidSubscribers = proStatus !== 'skipped' && parseInt( paidSubscribers ) > 0;
+ const [ isDisabled, setIsDisabled ] = useState( ! hasPaidSubscribers );
+
+ const { enqueueSubscriberImport } = useSubscriberImportMutation();
+
+ const importSubscribers = () => {
+ enqueueSubscriberImport( siteId, 'substack', 'summary' );
+ };
+
+ const onChange = ( { target: { checked } }: ChangeEvent< HTMLInputElement > ) =>
+ setIsDisabled( checked );
+
+ if ( status === 'skipped' ) {
+ return (
+
+
+ Subscriber importing was skipped!
+
+
+ );
+ }
+
+ if ( status === 'pending' ) {
+ return (
+
+
Here's an overview of what you'll migrate:
+
+
+ { cardData?.meta?.email_count } subscribers
+
+ { hasPaidSubscribers && (
+
+
+ { cardData?.meta?.paid_subscribers_count } paid subscribers
+
+ ) }
+ { hasPaidSubscribers && (
+ <>
+
+ Before we import your newsletter
+
+
+
+ To prevent any unexpected actions by your old provider , go to your
+ Stripe dashboard and click “Revoke access” for any service previously associated with
+ this subscription.
+
+
+
+
+
+ I’ve disconnected other providers from the Stripe account
+
+
+ >
+ ) }
+
+ Import subscribers
+
+
+ );
+ }
+
+ if ( status === 'importing' ) {
+ return (
+
+
+ Importing subscribers...
+
+
+ );
+ }
+
+ if ( status === 'done' ) {
+ const paid_subscribers = cardData?.meta?.paid_subscribers_count ?? 0;
+ const free_subscribers = cardData?.meta?.subscribed_count - paid_subscribers;
+ return (
+
+
We migrated { cardData.meta.subscribed_count } subscribers
+
+
+ { free_subscribers } free subscribers
+
+ { hasPaidSubscribers && (
+
+
+ { cardData.meta.paid_subscribers_count } paid subscribers
+
+ ) }
+
+ );
+ }
+}
diff --git a/client/my-sites/marketplace/components/progressbar/index.tsx b/client/my-sites/marketplace/components/progressbar/index.tsx
index bb0788de14836f..58b6bf74d5826f 100644
--- a/client/my-sites/marketplace/components/progressbar/index.tsx
+++ b/client/my-sites/marketplace/components/progressbar/index.tsx
@@ -104,7 +104,7 @@ export default function MarketplaceProgressBar( {
{ stepValue }
{ steps.length > 1 &&
{ stepIndication }
}
diff --git a/client/my-sites/plans-features-main/hooks/use-default-wpcom-plans-intent.ts b/client/my-sites/plans-features-main/hooks/use-default-wpcom-plans-intent.ts
new file mode 100644
index 00000000000000..29f48a41ea6bc7
--- /dev/null
+++ b/client/my-sites/plans-features-main/hooks/use-default-wpcom-plans-intent.ts
@@ -0,0 +1,18 @@
+import type { PlansIntent } from '@automattic/plans-grid-next';
+
+/**
+ * Used for defining the default plans intent for general WordPress.com plans UI.
+ *
+ * The default intent is used in various scenarios, such as:
+ * - signup flows / plans page that do not require a tailored plans mix
+ * - switching to the default plans through an escape hatch (button) when a tailored mix is rendered
+ * - showing the default plans in the comparison grid when a tailored mix is rendered otherwise
+ *
+ * When experimenting with different default plans, this hook can be used to define the intent.
+ * We will need an exclusion mechanism in that case (to not mix with other intents).
+ */
+const useDefaultWpcomPlansIntent = (): PlansIntent | undefined => {
+ return 'plans-default-wpcom';
+};
+
+export default useDefaultWpcomPlansIntent;
diff --git a/client/my-sites/plans-features-main/index.tsx b/client/my-sites/plans-features-main/index.tsx
index 7687a36aafc90a..2462aa849054c2 100644
--- a/client/my-sites/plans-features-main/index.tsx
+++ b/client/my-sites/plans-features-main/index.tsx
@@ -68,6 +68,7 @@ import PlanUpsellModal from './components/plan-upsell-modal';
import { useModalResolutionCallback } from './components/plan-upsell-modal/hooks/use-modal-resolution-callback';
import PlansPageSubheader from './components/plans-page-subheader';
import useCheckPlanAvailabilityForPurchase from './hooks/use-check-plan-availability-for-purchase';
+import useDefaultWpcomPlansIntent from './hooks/use-default-wpcom-plans-intent';
import useFilteredDisplayedIntervals from './hooks/use-filtered-displayed-intervals';
import useGenerateActionHook from './hooks/use-generate-action-hook';
import usePlanBillingPeriod from './hooks/use-plan-billing-period';
@@ -288,9 +289,14 @@ const PlansFeaturesMain = ( {
const intentFromSiteMeta = usePlanIntentFromSiteMeta();
const planFromUpsells = usePlanFromUpsells();
+ const defaultWpcomPlansIntent = useDefaultWpcomPlansIntent();
const [ forceDefaultPlans, setForceDefaultPlans ] = useState( false );
-
const [ intent, setIntent ] = useState< PlansIntent | undefined >( undefined );
+ /**
+ * Keep the `useEffect` here strictly about intent resolution.
+ * This is fairly critical logic and may generate side effects if not handled properly.
+ * Let's be especially deliberate about making changes.
+ */
useEffect( () => {
if ( intentFromSiteMeta.processing ) {
return;
@@ -299,13 +305,13 @@ const PlansFeaturesMain = ( {
// TODO: plans from upsell takes precedence for setting intent right now
// - this is currently set to the default wpcom set until we have updated tailored features for all plans
// - at which point, we'll inject the upsell plan to the tailored plans mix instead
- if ( 'plans-default-wpcom' !== intent && forceDefaultPlans ) {
- setIntent( 'plans-default-wpcom' );
+ if ( defaultWpcomPlansIntent !== intent && forceDefaultPlans ) {
+ setIntent( defaultWpcomPlansIntent );
} else if ( ! intent ) {
setIntent(
planFromUpsells
- ? 'plans-default-wpcom'
- : intentFromProps || intentFromSiteMeta.intent || 'plans-default-wpcom'
+ ? defaultWpcomPlansIntent
+ : intentFromProps || intentFromSiteMeta.intent || defaultWpcomPlansIntent
);
}
}, [
@@ -315,10 +321,11 @@ const PlansFeaturesMain = ( {
planFromUpsells,
forceDefaultPlans,
intentFromSiteMeta.processing,
+ defaultWpcomPlansIntent,
] );
const showEscapeHatch =
- intentFromSiteMeta.intent && ! isInSignup && 'plans-default-wpcom' !== intent;
+ intentFromSiteMeta.intent && ! isInSignup && defaultWpcomPlansIntent !== intent;
const eligibleForFreeHostingTrial = useSelector( isUserEligibleForFreeHostingTrial );
@@ -372,7 +379,7 @@ const PlansFeaturesMain = ( {
eligibleForFreeHostingTrial,
hasRedeemedDomainCredit: currentPlan?.hasRedeemedDomainCredit,
hiddenPlans,
- intent,
+ intent: shouldForceDefaultPlansBasedOnIntent( intent ) ? defaultWpcomPlansIntent : intent,
isDisplayingPlansNeededForFeature,
isSubdomainNotGenerated: ! resolvedSubdomainName.result,
selectedFeature,
@@ -383,7 +390,6 @@ const PlansFeaturesMain = ( {
term,
useCheckPlanAvailabilityForPurchase,
useFreeTrialPlanSlugs,
- forceDefaultIntent: shouldForceDefaultPlansBasedOnIntent( intent ),
} );
// we need only the visible ones for features grid (these should extend into plans-ui data store selectors)
@@ -615,7 +621,10 @@ const PlansFeaturesMain = ( {
} );
const isLoadingGridPlans = Boolean(
- ! intent || ! gridPlansForFeaturesGrid || ! gridPlansForComparisonGrid
+ ! intent ||
+ ! defaultWpcomPlansIntent || // this may be unnecessary, but just in case
+ ! gridPlansForFeaturesGrid ||
+ ! gridPlansForComparisonGrid
);
const isPlansGridReady = ! isLoadingGridPlans && ! resolvedSubdomainName.isLoading;
diff --git a/client/my-sites/plans/jetpack-plans/product-store/items-list/all-items.tsx b/client/my-sites/plans/jetpack-plans/product-store/items-list/all-items.tsx
index 15de5f6f4a2a17..7f3920bcb4abcc 100644
--- a/client/my-sites/plans/jetpack-plans/product-store/items-list/all-items.tsx
+++ b/client/my-sites/plans/jetpack-plans/product-store/items-list/all-items.tsx
@@ -1,6 +1,5 @@
import { isEnabled } from '@automattic/calypso-config';
import {
- isJetpackAISlug,
isJetpackPlanSlug,
isJetpackSocialSlug,
isJetpackStatsPaidProductSlug,
@@ -105,7 +104,6 @@ export const AllItems: React.FC< AllItemsProps > = ( {
const isMultiPlanSelectProduct =
( isJetpackSocialSlug( item.productSlug ) &&
! isEnabled( 'jetpack/social-plans-v1' ) ) ||
- isJetpackAISlug( item.productSlug ) ||
isJetpackStatsPaidProductSlug( item.productSlug );
let ctaHref = getCheckoutURL( item );
diff --git a/client/my-sites/plans/jetpack-plans/slug-to-selector-product.ts b/client/my-sites/plans/jetpack-plans/slug-to-selector-product.ts
index cd6d290bca36b7..a5e4c57930e896 100644
--- a/client/my-sites/plans/jetpack-plans/slug-to-selector-product.ts
+++ b/client/my-sites/plans/jetpack-plans/slug-to-selector-product.ts
@@ -32,6 +32,7 @@ import {
getJetpackProductRecommendedFor,
getJetpackPlanAlsoIncludedFeatures,
TERM_TRIENNIALLY,
+ isJetpackAISlug,
} from '@automattic/calypso-products';
import { getProductPartsFromAlias } from 'calypso/my-sites/checkout/src/hooks/use-prepare-products-for-cart';
import {
@@ -103,7 +104,11 @@ function slugToItem( slug: string ): Plan | Product | SelectorProduct | null | u
return null;
}
-function getDisclaimerLink() {
+function getDisclaimerLink( item: Product | Plan ) {
+ if ( objectIsProduct( item ) && isJetpackAISlug( item.product_slug ) ) {
+ return 'https://jetpack.com/redirect/?source=ai-assistant-fair-usage-policy';
+ }
+
const backupStorageFaqId = 'backup-storage-limits-lightbox-faq';
return `#${ backupStorageFaqId }`;
}
@@ -190,7 +195,11 @@ function itemToSelectorProduct(
features: {
items: features,
},
- disclaimer: getJetpackProductDisclaimer( item.product_slug, features, getDisclaimerLink() ),
+ disclaimer: getJetpackProductDisclaimer(
+ item.product_slug,
+ features,
+ getDisclaimerLink( item )
+ ),
quantity,
};
}
@@ -234,7 +243,11 @@ function itemToSelectorProduct(
features: {
items: buildCardFeaturesFromItem( item ),
},
- disclaimer: getJetpackProductDisclaimer( item.getStoreSlug(), features, getDisclaimerLink() ),
+ disclaimer: getJetpackProductDisclaimer(
+ item.getStoreSlug(),
+ features,
+ getDisclaimerLink( item )
+ ),
legacy: ! isResetPlan,
};
}
diff --git a/client/my-sites/plugins/controller-logged-in.js b/client/my-sites/plugins/controller-logged-in.js
index e2d7f4aec6f6f5..1cde9fd01198f6 100644
--- a/client/my-sites/plugins/controller-logged-in.js
+++ b/client/my-sites/plugins/controller-logged-in.js
@@ -1,3 +1,6 @@
+import { removeQueryArgs } from '@wordpress/url';
+import { translate } from 'i18n-calypso';
+import { successNotice } from 'calypso/state/notices/actions';
import Plans from './plans';
import PluginUpload from './plugin-upload';
@@ -6,6 +9,22 @@ export function upload( context, next ) {
next();
}
+export function maybeShowUpgradeSuccessNotice( context, next ) {
+ if ( context.query.showUpgradeSuccessNotice ) {
+ // Bump the notice to the back of the callstack so it is called after client render.
+ setTimeout( () => {
+ context.store.dispatch(
+ successNotice( translate( 'Thank you for your purchase!' ), {
+ id: 'plugin-upload-upgrade-plan-success',
+ duration: 5000,
+ } )
+ );
+ }, 0 );
+ context.page.replace( removeQueryArgs( context.canonicalPath, 'showUpgradeSuccessNotice' ) );
+ }
+ next();
+}
+
export function plans( context, next ) {
context.primary = (
isRequestingForAllSites( state ) );
+
const toggleDisplayManageSitePluginsModal = useCallback( () => {
setDisplayManageSitePluginsModal( ( value ) => ! value );
}, [] );
@@ -525,6 +528,7 @@ function ManageSitesButton( { plugin, installedOnSitesQuantity } ) {
{ translate( 'Manage sites' ) }
diff --git a/client/my-sites/plugins/plugins-browser-item/index.jsx b/client/my-sites/plugins/plugins-browser-item/index.jsx
index ff029a45561d65..59d4645c3baa15 100644
--- a/client/my-sites/plugins/plugins-browser-item/index.jsx
+++ b/client/my-sites/plugins/plugins-browser-item/index.jsx
@@ -220,16 +220,18 @@ const PluginsBrowserListElement = ( props ) => {
onClick={ onClickItem }
>
-
-
{ plugin.name }
- { variant === PluginsBrowserElementVariant.Extended && (
- <>
-
- { translate( 'by ' ) }
- { plugin.author_name }
-
- >
- ) }
+
+
+
{ plugin.name }
+ { variant === PluginsBrowserElementVariant.Extended && (
+ <>
+
+ { translate( 'by ' ) }
+ { plugin.author_name }
+
+ >
+ ) }
+
{ plugin.short_description }
{ isUntestedVersion && (
@@ -434,9 +436,11 @@ function Placeholder( { variant } ) {
diff --git a/client/my-sites/plugins/plugins-browser-item/style.scss b/client/my-sites/plugins/plugins-browser-item/style.scss
index ccde622053ac00..92312a7a2fc151 100644
--- a/client/my-sites/plugins/plugins-browser-item/style.scss
+++ b/client/my-sites/plugins/plugins-browser-item/style.scss
@@ -36,6 +36,14 @@
margin-left: calc(47px + 16px); // icon width + margin
}
+ .plugins-browser-item__title {
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ white-space: unset;
+ }
+
.plugins-browser-item__ratings {
display: flex;
justify-content: flex-end;
@@ -62,7 +70,7 @@
}
.plugins-browser-item__description {
- margin: 24px 0;
+ margin: 12px 0 24px 0;
font-family: "SF Pro Text", $sans;
font-size: $font-body-small;
font-weight: 400;
@@ -70,9 +78,9 @@
color: $studio-gray-80;
// limit to 2 lines
- height: calc(20px * 2); // line height * number of lines
+ height: calc(20px * 3); // line height * number of lines
display: -webkit-box;
- -webkit-line-clamp: 2;
+ -webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
@@ -179,11 +187,15 @@
}
}
+.plugins-browser-item__header {
+ min-height: 66px;
+}
+
.plugins-browser-item__title {
color: var(--color-neutral-100);
font-weight: 500;
font-size: $font-body;
- line-height: 24px;
+ line-height: 20px;
}
.plugins-browser-item__author {
diff --git a/client/my-sites/purchases/payment-methods/index.tsx b/client/my-sites/purchases/payment-methods/index.tsx
index bfdcf7dba8784f..93b3ec8f274e62 100644
--- a/client/my-sites/purchases/payment-methods/index.tsx
+++ b/client/my-sites/purchases/payment-methods/index.tsx
@@ -26,6 +26,7 @@ import { useCreateCreditCard } from 'calypso/my-sites/checkout/src/hooks/use-cre
import { logStashLoadErrorEvent } from 'calypso/my-sites/checkout/src/lib/analytics';
import PurchasesNavigation from 'calypso/my-sites/purchases/navigation';
import { useDispatch, useSelector } from 'calypso/state';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
import { getCurrentUserLocale } from 'calypso/state/current-user/selectors';
import { errorNotice } from 'calypso/state/notices/actions';
import { getAddNewPaymentMethodUrlFor, getPaymentMethodsUrlFor } from '../paths';
@@ -84,9 +85,11 @@ function SiteLevelAddNewPaymentMethodForm( { siteSlug }: { siteSlug: string } )
const logPaymentMethodsError = useLogPaymentMethodsError(
'site level add new payment method load error'
);
+ const currency = useSelector( getCurrentUserCurrencyCode );
const { isStripeLoading, stripeLoadingError } = useStripe();
const stripeMethod = useCreateCreditCard( {
+ currency,
isStripeLoading,
stripeLoadingError,
shouldUseEbanx: false,
diff --git a/client/my-sites/sidebar/static-data/fallback-menu.js b/client/my-sites/sidebar/static-data/fallback-menu.js
index 1c03f50a482116..78546c3917a64f 100644
--- a/client/my-sites/sidebar/static-data/fallback-menu.js
+++ b/client/my-sites/sidebar/static-data/fallback-menu.js
@@ -28,6 +28,8 @@ const WOOCOMMERCE_ICON = `data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3
export default function buildFallbackResponse( {
siteDomain = '',
+ isAtomic,
+ isPlanExpired,
shouldShowMailboxes = false,
shouldShowLinks = false,
shouldShowTestimonials = false,
@@ -600,7 +602,10 @@ export default function buildFallbackResponse( {
{
parent: 'options-general.php',
slug: 'options-hosting-configuration-php',
- title: translate( 'Hosting Configuration' ),
+ title:
+ isAtomic && ! isPlanExpired
+ ? translate( 'Server Settings' )
+ : translate( 'Hosting Features' ),
type: 'submenu-item',
url: `/hosting-config/${ siteDomain }`,
},
diff --git a/client/my-sites/sidebar/use-site-menu-items.js b/client/my-sites/sidebar/use-site-menu-items.js
index 029309154c8a80..a4ba433d0b65b2 100644
--- a/client/my-sites/sidebar/use-site-menu-items.js
+++ b/client/my-sites/sidebar/use-site-menu-items.js
@@ -16,7 +16,7 @@ import isAtomicSite from 'calypso/state/selectors/is-site-automated-transfer';
import isSiteWpcomStaging from 'calypso/state/selectors/is-site-wpcom-staging';
import isSiteWPForTeams from 'calypso/state/selectors/is-site-wpforteams';
import { getSiteDomain, isJetpackSite } from 'calypso/state/sites/selectors';
-import { getSelectedSiteId } from 'calypso/state/ui/selectors';
+import { getSelectedSite, getSelectedSiteId } from 'calypso/state/ui/selectors';
import { requestAdminMenu } from '../../state/admin-menu/actions';
import allSitesMenu from './static-data/all-sites-menu';
import buildFallbackResponse from './static-data/fallback-menu';
@@ -32,6 +32,7 @@ const useSiteMenuItems = () => {
const isJetpack = useSelector( ( state ) => isJetpackSite( state, selectedSiteId ) );
const isAtomic = useSelector( ( state ) => isAtomicSite( state, selectedSiteId ) );
const isStagingSite = useSelector( ( state ) => isSiteWpcomStaging( state, selectedSiteId ) );
+ const isPlanExpired = useSelector( ( state ) => !! getSelectedSite( state )?.plan?.expired );
const locale = useLocale();
const isAllDomainsView = '/domains/manage' === currentRoute;
const { currentSection } = useCurrentRoute();
@@ -109,6 +110,8 @@ const useSiteMenuItems = () => {
*/
const fallbackDataOverrides = {
siteDomain,
+ isAtomic,
+ isPlanExpired,
shouldShowWooCommerce,
shouldShowThemes,
shouldShowMailboxes,
diff --git a/client/my-sites/sidebar/utils.js b/client/my-sites/sidebar/utils.js
index 401a548709c0bb..4d54f772a557de 100644
--- a/client/my-sites/sidebar/utils.js
+++ b/client/my-sites/sidebar/utils.js
@@ -40,11 +40,11 @@ export const itemLinkMatches = ( path, currentPath ) => {
}
if ( pathIncludes( currentPath, 'plugins', 1 ) ) {
- if ( pathIncludes( currentPath, 'browse', 2 ) ) {
- return pathIncludes( path, 'plugins', 1 ) && ! pathIncludes( path, 'scheduled-updates', 2 );
+ if ( pathIncludes( currentPath, 'scheduled-updates', 2 ) ) {
+ return pathIncludes( path, 'plugins', 1 ) && pathIncludes( path, 'scheduled-updates', 2 );
}
- return pathIncludes( path, 'plugins', 1 ) && fragmentIsEqual( path, currentPath, 2 );
+ return pathIncludes( path, 'plugins', 1 ) && fragmentIsEqual( path, currentPath, 1 );
}
if ( pathIncludes( currentPath, 'settings', 1 ) ) {
diff --git a/client/my-sites/site-monitoring/components/time-range-picker/index.jsx b/client/my-sites/site-monitoring/components/time-range-picker/index.jsx
index 6a44bc4a128891..7c5eb6acfedeea 100644
--- a/client/my-sites/site-monitoring/components/time-range-picker/index.jsx
+++ b/client/my-sites/site-monitoring/components/time-range-picker/index.jsx
@@ -67,7 +67,7 @@ export const TimeDateChartControls = ( { onTimeRangeChange } ) => {
{ translate( 'Time range' ) }
-
+
{ options.map( ( option ) => {
return (
,
- footer: translate( '%(percent)d%% of views', {
- args: { percent: bestViewsEverPercent || 0 },
+ footer: translate( '%(percent)s of views', {
+ args: { percent: formatPercentage( bestViewsEverPercent, true ) },
context: 'Stats: Percentage of views',
} ),
},
diff --git a/client/my-sites/stats/components/stats-button/stats-button.tsx b/client/my-sites/stats/components/stats-button/stats-button.tsx
index 619e7067bc94cc..0641828e306505 100644
--- a/client/my-sites/stats/components/stats-button/stats-button.tsx
+++ b/client/my-sites/stats/components/stats-button/stats-button.tsx
@@ -8,6 +8,7 @@ import { getSelectedSiteId } from 'calypso/state/ui/selectors';
interface StatsButtonProps extends React.ButtonHTMLAttributes< HTMLButtonElement > {
children?: React.ReactNode;
primary?: boolean;
+ busy?: boolean;
}
const StatsButton: React.FC< StatsButtonProps > = ( { children, primary, ...rest } ) => {
@@ -23,6 +24,7 @@ const StatsButton: React.FC< StatsButtonProps > = ( { children, primary, ...rest
} ) }
variant="primary"
primary={ isWPCOMSite ? true : undefined }
+ isBusy={ rest.busy }
{ ...rest }
>
{ children }
diff --git a/client/my-sites/stats/feedback/index.tsx b/client/my-sites/stats/feedback/index.tsx
index 84b35c1f0b21c6..82c191dcf751f9 100644
--- a/client/my-sites/stats/feedback/index.tsx
+++ b/client/my-sites/stats/feedback/index.tsx
@@ -1,24 +1,124 @@
+import { Button } from '@wordpress/components';
+import { close } from '@wordpress/icons';
+import { useTranslate } from 'i18n-calypso';
import { useState } from 'react';
import FeedbackModal from './modal';
import './style.scss';
-function StatsFeedbackCard() {
- // A simple card component with feedback buttons.
- const [ isOpen, setIsOpen ] = useState( false );
+const FEEDBACK_ACTION_LEAVE_REVIEW = 'feedback-action-leave-review';
+const FEEDBACK_ACTION_SEND_FEEDBACK = 'feedback-action-send-feedback';
+const FEEDBACK_ACTION_DISMISS_FLOATING_PANEL = 'feedback-action-dismiss-floating-panel';
+
+const FEEDBACK_LEAVE_REVIEW_URL = 'https://wordpress.org/support/plugin/jetpack/reviews/';
+
+interface FeedbackProps {
+ siteId: number;
+}
+
+interface FeedbackPropsInternal {
+ clickHandler: ( action: string ) => void;
+ isOpen?: boolean;
+}
+
+function FeedbackContent( { clickHandler }: FeedbackPropsInternal ) {
+ const translate = useTranslate();
+
+ const ctaText = translate( 'How do you rate your overall experience with Jetpack Stats?' );
+ const primaryButtonText = translate( 'Love it? Leave a review ↗' );
+ const secondaryButtonText = translate( 'Not a fan? Help us improve' );
+
+ const handleLeaveReview = () => {
+ clickHandler( FEEDBACK_ACTION_LEAVE_REVIEW );
+ };
+
+ const handleSendFeedback = () => {
+ clickHandler( FEEDBACK_ACTION_SEND_FEEDBACK );
+ };
return (
-
-
-
Hello from StatsFeedbackCard
+
+
{ ctaText }
+
+
+ 😍
+ { primaryButtonText }
+
+
+ 😠
+ { secondaryButtonText }
+
-
- one
- setIsOpen( true ) }>two
-
-
setIsOpen( false ) } />
);
}
-export default StatsFeedbackCard;
+function FeedbackPanel( { isOpen, clickHandler }: FeedbackPropsInternal ) {
+ const translate = useTranslate();
+
+ const handleCloseButtonClicked = () => {
+ clickHandler( FEEDBACK_ACTION_DISMISS_FLOATING_PANEL );
+ };
+
+ if ( ! isOpen ) {
+ return null;
+ }
+
+ return (
+
+
+
+
+ { translate( 'Dismiss' ) }
+
+
+ );
+}
+
+function FeedbackCard( { clickHandler }: FeedbackPropsInternal ) {
+ return (
+
+
+
+ );
+}
+
+function StatsFeedbackController( { siteId }: FeedbackProps ) {
+ const [ isOpen, setIsOpen ] = useState( false );
+ const [ isFloatingPanelOpen, setIsFloatingPanelOpen ] = useState( true );
+
+ const handleButtonClick = ( action: string ) => {
+ switch ( action ) {
+ case FEEDBACK_ACTION_SEND_FEEDBACK:
+ setIsOpen( true );
+ break;
+ case FEEDBACK_ACTION_DISMISS_FLOATING_PANEL:
+ setIsFloatingPanelOpen( false );
+ break;
+ case FEEDBACK_ACTION_LEAVE_REVIEW:
+ setIsFloatingPanelOpen( false );
+ window.open( FEEDBACK_LEAVE_REVIEW_URL );
+ break;
+ // Ignore other cases.
+ }
+ };
+
+ return (
+
+
+
+ { isOpen && setIsOpen( false ) } /> }
+
+ );
+}
+
+export default StatsFeedbackController;
diff --git a/client/my-sites/stats/feedback/modal/index.tsx b/client/my-sites/stats/feedback/modal/index.tsx
index 7ec358ea08f533..3bf414bfdfbb7a 100644
--- a/client/my-sites/stats/feedback/modal/index.tsx
+++ b/client/my-sites/stats/feedback/modal/index.tsx
@@ -1,32 +1,66 @@
import { Button, Modal, TextareaControl } from '@wordpress/components';
import { close } from '@wordpress/icons';
import { useTranslate } from 'i18n-calypso';
-import React, { useState } from 'react';
+import React, { useState, useCallback, useEffect } from 'react';
import StatsButton from 'calypso/my-sites/stats/components/stats-button';
+import { useDispatch } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { successNotice } from 'calypso/state/notices/actions';
+import useSubmitProductFeedback from './use-submit-product-feedback';
import './style.scss';
interface ModalProps {
- isOpen: boolean;
+ siteId: number;
onClose: () => void;
}
-const FeedbackModal: React.FC< ModalProps > = ( { isOpen, onClose } ) => {
+const FeedbackModal: React.FC< ModalProps > = ( { siteId, onClose } ) => {
const translate = useTranslate();
- const [ isAnimating, setIsAnimating ] = useState( false );
+ const dispatch = useDispatch();
const [ content, setContent ] = useState( '' );
- const handleClose = () => {
- setIsAnimating( true );
+ const { isSubmittingFeedback, submitFeedback, isSubmissionSuccessful } =
+ useSubmitProductFeedback( siteId );
+
+ const handleClose = useCallback( () => {
setTimeout( () => {
- setIsAnimating( false );
onClose();
}, 200 );
- };
+ }, [ onClose ] );
+
+ const onFormSubmit = useCallback( () => {
+ if ( ! content ) {
+ return;
+ }
+
+ dispatch(
+ recordTracksEvent( 'calypso_jetpack_stats_user_feedback_form_submit', {
+ feedback: content,
+ } )
+ );
+
+ const sourceUrl = `${ window.location.origin }${ window.location.pathname }`;
+ submitFeedback( {
+ source_url: sourceUrl,
+ product_name: 'Jetpack Stats',
+ feedback: content,
+ is_testing: true,
+ } );
+ }, [ dispatch, content, submitFeedback ] );
+
+ useEffect( () => {
+ if ( isSubmissionSuccessful ) {
+ dispatch(
+ successNotice( translate( 'Thank you for your feedback!' ), {
+ id: 'submit-product-feedback-success',
+ duration: 5000,
+ } )
+ );
- if ( ! isOpen && ! isAnimating ) {
- return null;
- }
+ handleClose();
+ }
+ }, [ dispatch, isSubmissionSuccessful, handleClose, translate ] );
return (
@@ -56,7 +90,14 @@ const FeedbackModal: React.FC< ModalProps > = ( { isOpen, onClose } ) => {
onChange={ setContent }
/>
- { translate( 'Submit' ) }
+
+ { translate( 'Submit' ) }
+
diff --git a/client/my-sites/stats/feedback/modal/use-submit-product-feedback-mutation.ts b/client/my-sites/stats/feedback/modal/use-submit-product-feedback-mutation.ts
new file mode 100644
index 00000000000000..9586770bc2dbe2
--- /dev/null
+++ b/client/my-sites/stats/feedback/modal/use-submit-product-feedback-mutation.ts
@@ -0,0 +1,29 @@
+import { useMutation, UseMutationOptions, UseMutationResult } from '@tanstack/react-query';
+import wpcom from 'calypso/lib/wp';
+import type { SubmitJetpackStatsFeedbackParams } from './use-submit-product-feedback';
+import type { APIError } from 'calypso/jetpack-cloud/sections/agency-dashboard/sites-overview/types';
+
+interface APIResponse {
+ success: boolean;
+}
+
+function mutationSubmitProductFeedback(
+ params: SubmitJetpackStatsFeedbackParams,
+ siteId?: number
+): Promise< APIResponse > {
+ return wpcom.req.post( {
+ apiNamespace: 'wpcom/v2',
+ path: `/sites/${ siteId }/jetpack-stats/user-feedback`,
+ body: params,
+ } );
+}
+
+export default function useSubmitProductFeedbackMutation< TContext = unknown >(
+ siteId?: number,
+ options?: UseMutationOptions< APIResponse, APIError, SubmitJetpackStatsFeedbackParams, TContext >
+): UseMutationResult< APIResponse, APIError, SubmitJetpackStatsFeedbackParams, TContext > {
+ return useMutation< APIResponse, APIError, SubmitJetpackStatsFeedbackParams, TContext >( {
+ ...options,
+ mutationFn: ( params ) => mutationSubmitProductFeedback( params, siteId ),
+ } );
+}
diff --git a/client/my-sites/stats/feedback/modal/use-submit-product-feedback.ts b/client/my-sites/stats/feedback/modal/use-submit-product-feedback.ts
new file mode 100644
index 00000000000000..89333254e58625
--- /dev/null
+++ b/client/my-sites/stats/feedback/modal/use-submit-product-feedback.ts
@@ -0,0 +1,49 @@
+import { useTranslate } from 'i18n-calypso';
+import { useEffect } from 'react';
+import { useDispatch } from 'calypso/state';
+import { errorNotice } from 'calypso/state/notices/actions';
+import useSubmitProductFeedbackMutation from './use-submit-product-feedback-mutation';
+
+export interface SubmitJetpackStatsFeedbackParams {
+ source_url: string;
+ product_name: string;
+ feedback: string;
+ // TODO: Remove this flag once we're ready to send real feedback.
+ is_testing?: boolean;
+}
+
+export default function useSubmitProductFeedback( siteId: number ): {
+ isSubmittingFeedback: boolean;
+ submitFeedback: ( params: SubmitJetpackStatsFeedbackParams ) => void;
+ isSubmissionSuccessful: boolean;
+ resetMutation: () => void;
+} {
+ const translate = useTranslate();
+ const dispatch = useDispatch();
+
+ const {
+ isError,
+ isSuccess,
+ mutate,
+ isPending: isSubmittingFeedback,
+ reset,
+ } = useSubmitProductFeedbackMutation( siteId );
+
+ useEffect( () => {
+ if ( isError ) {
+ dispatch(
+ errorNotice( translate( 'Something went wrong. Please try again.' ), {
+ id: 'submit-product-feedback-failure',
+ duration: 5000,
+ } )
+ );
+ }
+ }, [ translate, isError, dispatch ] );
+
+ return {
+ isSubmittingFeedback,
+ submitFeedback: mutate,
+ isSubmissionSuccessful: isSuccess,
+ resetMutation: reset,
+ };
+}
diff --git a/client/my-sites/stats/feedback/style.scss b/client/my-sites/stats/feedback/style.scss
index 640544a4c02c55..05756925b6fa95 100644
--- a/client/my-sites/stats/feedback/style.scss
+++ b/client/my-sites/stats/feedback/style.scss
@@ -1,8 +1,127 @@
+@import "@automattic/components/src/styles/typography";
+@import "@wordpress/base-styles/breakpoints";
+
+.stats-feedback-content {
+ font-family: $font-sf-pro-text;
+ font-size: $font-body-small;
+ font-weight: 400;
+ line-height: 21px;
+ letter-spacing: -0.24px;
+ color: var(--studio-gray-100);
+}
+
+.stats-feedback-content__actions {
+ display: flex;
+ flex-direction: column;
+
+ .components-button {
+ width: fit-content;
+ font-weight: 500;
+ font-size: $font-body-small;
+ border-radius: 4px;
+
+ &:not(:last-child) {
+ margin-bottom: 8px;
+ }
+ }
+}
+
+.stats-feedback-content__cta {
+ margin: 0 16px 16px 0;
+}
+
+.stats-feedback-content__emoji {
+ font-size: larger;
+ margin-right: 6px;
+}
+
.stats-feedback-card {
background: var(--studio-white);
border: 1px solid var(--studio-gray-5);
border-radius: 5px; // stylelint-disable-line scales/radii
padding: 20px;
- margin: 32px;
+
+ @media (max-width: $break-medium) {
+ border-radius: 0;
+ border-top: none;
+ border-left: none;
+ border-right: none;
+ }
+
+ .stats-feedback-content {
+ display: flex;
+ flex-direction: column;
+
+ @media (min-width: $break-wide) {
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
+ }
+ }
+
+ .stats-feedback-content__cta {
+ @media (min-width: $break-wide) {
+ margin-right: 12px;
+ margin-bottom: 0;
+ }
+ }
+
+ .stats-feedback-content__actions {
+ @media (min-width: $break-wide) {
+ flex-direction: row;
+
+ .components-button {
+ margin-left: 6px;
+ margin-bottom: 0;
+ }
+ }
+ @media (max-width: $break-mobile) {
+ .components-button {
+ width: 100%;
+ }
+ }
+ }
+}
+
+.stats-feedback-panel {
+ position: fixed;
+ bottom: 24px;
+ right: 24px;
+ z-index: 10;
+
+ border: 1px solid var(--studio-gray-5);
+ border-radius: 8px; // stylelint-disable-line scales/radii
+ padding: 24px;
+ width: 300px;
+ box-sizing: border-box;
+ box-shadow: 0 10px 20px 0 #00000014;
+
+ background-color: var(--studio-white);
+
+ .components-button.is-link {
+ color: var(--studio-gray-100);
+ }
+}
+
+.stats-feedback-panel__close-button {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+
+ svg {
+ width: 14px;
+ height: 14px;
+ }
+}
+
+.stats-feedback-panel__dismiss-button {
+ font-family: $font-sf-pro-text;
+ font-size: $font-body-small;
+ font-weight: 500;
+ line-height: 21px;
+
+ &.components-button {
+ margin-top: 16px;
+ }
}
diff --git a/client/my-sites/stats/modernized-stats-table-styles.scss b/client/my-sites/stats/modernized-stats-table-styles.scss
index dd1c278a7497d7..605424231a060c 100644
--- a/client/my-sites/stats/modernized-stats-table-styles.scss
+++ b/client/my-sites/stats/modernized-stats-table-styles.scss
@@ -108,7 +108,6 @@ $common-border-radius: 4px;
}
table {
- table-layout: fixed;
min-width: 900px;
td,
diff --git a/client/my-sites/stats/site.jsx b/client/my-sites/stats/site.jsx
index 140ef34009499c..0f553d06b9a21a 100644
--- a/client/my-sites/stats/site.jsx
+++ b/client/my-sites/stats/site.jsx
@@ -58,7 +58,7 @@ import StatsModuleSearch from './features/modules/stats-search';
import StatsModuleTopPosts from './features/modules/stats-top-posts';
import StatsModuleUTM, { StatsModuleUTMOverlay } from './features/modules/stats-utm';
import StatsModuleVideos from './features/modules/stats-videos';
-import StatsFeedbackCard from './feedback';
+import StatsFeedbackController from './feedback';
import HighlightsSection from './highlights-section';
import { shouldGateStats } from './hooks/use-should-gate-stats';
import MiniCarousel from './mini-carousel';
@@ -242,7 +242,7 @@ class StatsSite extends Component {
shouldForceDefaultDateRange,
} = this.props;
const isNewStateEnabled = config.isEnabled( 'stats/empty-module-traffic' );
- const isFeedbackCardEnabled = config.isEnabled( 'stats/user-feedback' );
+ const isUserFeedbackEnabled = config.isEnabled( 'stats/user-feedback' );
let defaultPeriod = PAST_SEVEN_DAYS;
const shouldShowUpsells = isOdysseyStats && ! isAtomic;
@@ -810,7 +810,7 @@ class StatsSite extends Component {
) }
- { isFeedbackCardEnabled &&
}
+ { isUserFeedbackEnabled &&
}
{ this.props.upsellModalView &&
}
diff --git a/client/my-sites/stats/stats-subscribers-highlight-section/index.tsx b/client/my-sites/stats/stats-subscribers-highlight-section/index.tsx
index 24a9b286467415..4867afa9639ab3 100644
--- a/client/my-sites/stats/stats-subscribers-highlight-section/index.tsx
+++ b/client/my-sites/stats/stats-subscribers-highlight-section/index.tsx
@@ -1,6 +1,6 @@
import {
ComponentSwapper,
- CountComparisonCard,
+ CountCard,
MobileHighlightCardListing,
Spinner,
} from '@automattic/components';
@@ -87,12 +87,12 @@ function SubscriberHighlightsStandard( {
return (
{ highlights.map( ( highlight ) => (
-
) ) }
diff --git a/client/my-sites/stats/stats-subscribers-overview/index.tsx b/client/my-sites/stats/stats-subscribers-overview/index.tsx
index f03c6aeca7c314..202d565e0cfff0 100644
--- a/client/my-sites/stats/stats-subscribers-overview/index.tsx
+++ b/client/my-sites/stats/stats-subscribers-overview/index.tsx
@@ -1,4 +1,4 @@
-import { CountComparisonCard } from '@automattic/components';
+import { CountCard } from '@automattic/components';
import React from 'react';
import useSubscribersOverview from 'calypso/my-sites/stats/hooks/use-subscribers-overview';
@@ -15,12 +15,11 @@ const SubscribersOverview: React.FC< SubscribersOverviewProps > = ( { siteId } )
{ overviewData.map( ( { count, heading }, index ) => {
return (
// TODO: Communicate loading vs error state to the user.
-
);
} ) }
diff --git a/client/my-sites/stats/wordads/highlights-section.jsx b/client/my-sites/stats/wordads/highlights-section.jsx
index ec6919396b5a36..e4af02d3c92561 100644
--- a/client/my-sites/stats/wordads/highlights-section.jsx
+++ b/client/my-sites/stats/wordads/highlights-section.jsx
@@ -131,7 +131,7 @@ function HighlightsListing( { highlights } ) {
}
value={ highlight.value }
/>
) ) }
diff --git a/client/package.json b/client/package.json
index 0f9e56a62bf335..500045e15cb3f5 100644
--- a/client/package.json
+++ b/client/package.json
@@ -27,7 +27,7 @@
"@automattic/calypso-sentry": "workspace:^",
"@automattic/calypso-stripe": "workspace:^",
"@automattic/calypso-url": "workspace:^",
- "@automattic/color-studio": "2.6.0",
+ "@automattic/color-studio": "^3.0.1",
"@automattic/command-palette": "workspace:^",
"@automattic/components": "workspace:^",
"@automattic/composite-checkout": "workspace:^",
@@ -94,7 +94,7 @@
"@wordpress/components": "^28.2.0",
"@wordpress/compose": "^7.2.0",
"@wordpress/data": "^10.2.0",
- "@wordpress/dataviews": "patch:@wordpress/dataviews@npm%3A0.4.1#~/.yarn/patches/@wordpress-dataviews-npm-0.4.1-2c01fa0792.patch",
+ "@wordpress/dataviews": "^4.2.0",
"@wordpress/dom": "^4.2.0",
"@wordpress/edit-post": "^8.2.0",
"@wordpress/element": "^6.2.0",
diff --git a/client/performance-profiler/components/charts/history-chart.jsx b/client/performance-profiler/components/charts/history-chart.jsx
index d62d9a5365327c..db0ee8dcdafd51 100644
--- a/client/performance-profiler/components/charts/history-chart.jsx
+++ b/client/performance-profiler/components/charts/history-chart.jsx
@@ -1,3 +1,4 @@
+import { useResizeObserver } from '@wordpress/compose';
import { Icon, info } from '@wordpress/icons';
import { extent as d3Extent, max as d3Max } from 'd3-array';
import { axisBottom as d3AxisBottom, axisLeft as d3AxisLeft } from 'd3-axis';
@@ -140,8 +141,8 @@ const showTooltip = ( tooltip, data, ev = null ) => {
tooltip.style( 'opacity', 1 );
tooltip
.html( data )
- .style( 'left', event.pageX - 28 + 'px' )
- .style( 'top', event.pageY - 50 + 'px' );
+ .style( 'left', event.layerX - 28 + 'px' )
+ .style( 'top', event.layerY - 50 + 'px' );
};
// Hide tooltip on mouse out
@@ -180,7 +181,7 @@ const generateSampleData = ( range ) => {
return data;
};
-const HistoryChart = ( { data, range, height, width } ) => {
+const HistoryChart = ( { data, range, height } ) => {
const svgRef = createRef();
const tooltipRef = createRef();
const dataAvailable = data && data.some( ( e ) => e.value !== null );
@@ -189,11 +190,17 @@ const HistoryChart = ( { data, range, height, width } ) => {
data = generateSampleData( range );
}
+ const [ resizeObserverRef, entry ] = useResizeObserver();
+
useEffect( () => {
+ if ( ! entry ) {
+ return;
+ }
// Clear previous chart
d3Select( svgRef.current ).selectAll( '*' ).remove();
- const margin = { top: 20, right: 0, bottom: 40, left: 40 };
+ const width = entry.width;
+ const margin = { top: 20, right: 20, bottom: 40, left: 40 };
const { xScale, yScale, colorScale } = createScales( data, range, margin, width, height );
@@ -208,7 +215,7 @@ const HistoryChart = ( { data, range, height, width } ) => {
const tooltip = d3Select( tooltipRef.current ).attr( 'class', 'tooltip' );
dataAvailable && drawDots( svg, data, xScale, yScale, colorScale, range, tooltip );
- }, [ dataAvailable, data, range, height, width ] );
+ }, [ dataAvailable, data, range, svgRef, tooltipRef, height, entry ] );
const handleInfoToolTip = ( event ) => {
const tooltip = d3Select( tooltipRef.current );
@@ -223,6 +230,7 @@ const HistoryChart = ( { data, range, height, width } ) => {
return (
+ { resizeObserverRef }
diff --git a/client/performance-profiler/components/charts/style.scss b/client/performance-profiler/components/charts/style.scss
index 49b9f141fcde6c..664c8704fc8d8b 100644
--- a/client/performance-profiler/components/charts/style.scss
+++ b/client/performance-profiler/components/charts/style.scss
@@ -2,7 +2,8 @@
.chart-container {
cursor: pointer;
- display: inline-block;
+ position: relative;
+ max-width: 550px;
.tick {
line {
diff --git a/client/performance-profiler/components/core-web-vitals-accordion/index.tsx b/client/performance-profiler/components/core-web-vitals-accordion/index.tsx
new file mode 100644
index 00000000000000..734487ad1b0cf9
--- /dev/null
+++ b/client/performance-profiler/components/core-web-vitals-accordion/index.tsx
@@ -0,0 +1,90 @@
+import { FoldableCard } from '@automattic/components';
+import { useTranslate } from 'i18n-calypso';
+import { Metrics } from 'calypso/data/site-profiler/types';
+import {
+ metricsNames,
+ mapThresholdsToStatus,
+ displayValue,
+} from 'calypso/performance-profiler/utils/metrics';
+import { StatusIndicator } from '../status-indicator';
+
+import './styles.scss';
+
+type Props = Record< Metrics, number > & {
+ activeTab: Metrics | null;
+ setActiveTab: ( tab: Metrics | null ) => void;
+ children: React.ReactNode;
+};
+type HeaderProps = {
+ displayName: string;
+ metricKey: Metrics;
+ metricValue: number;
+};
+
+const CardHeader = ( props: HeaderProps ) => {
+ const { displayName, metricKey, metricValue } = props;
+ return (
+
+
+
+ { displayName }
+
+ { displayValue( metricKey, metricValue ) }
+
+
+
+ );
+};
+
+export const CoreWebVitalsAccordion = ( props: Props ) => {
+ const { activeTab, setActiveTab, children } = props;
+ const translate = useTranslate();
+
+ const onClick = ( key: Metrics ) => {
+ // If the user clicks the current tab, close it.
+ if ( key === activeTab ) {
+ setActiveTab( null );
+ } else {
+ setActiveTab( key as Metrics );
+ }
+ };
+
+ return (
+
+ { Object.entries( metricsNames ).map( ( [ key, { displayName } ] ) => {
+ if ( props[ key as Metrics ] === undefined || props[ key as Metrics ] === null ) {
+ return null;
+ }
+
+ // Only display TBT if INP is not available
+ if ( key === 'tbt' && props[ 'inp' ] !== undefined && props[ 'inp' ] !== null ) {
+ return null;
+ }
+
+ return (
+
+ }
+ hideSummary
+ screenReaderText={ translate( 'More' ) }
+ compact
+ clickableHeader
+ smooth
+ iconSize={ 18 }
+ onClick={ () => onClick( key as Metrics ) }
+ expanded={ key === activeTab }
+ >
+ { children }
+
+ );
+ } ) }
+
+ );
+};
diff --git a/client/performance-profiler/components/core-web-vitals-accordion/styles.scss b/client/performance-profiler/components/core-web-vitals-accordion/styles.scss
new file mode 100644
index 00000000000000..74147294368f72
--- /dev/null
+++ b/client/performance-profiler/components/core-web-vitals-accordion/styles.scss
@@ -0,0 +1,42 @@
+$blueberry-color: #3858e9;
+
+.core-web-vitals-accordion {
+ .core-web-vitals-accordion__card {
+ border-top: 0;
+ .foldable-card__content {
+ border-top: 0;
+ }
+ &.foldable-card {
+ box-shadow: none;
+ border-top: 1px solid var(--studio-gray-5);
+
+ &:last-child {
+ border-bottom: 1px solid var(--studio-gray-5);
+ }
+
+ &.is-expanded .foldable-card__content {
+ border-top: 0;
+ max-height: fit-content;
+ }
+ }
+ .core-web-vitals-display__details {
+ border: 0;
+ }
+ }
+
+}
+
+.core-web-vitals-accordion__header {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ width: 100%;
+}
+
+.core-web-vitals-accordion__header-text {
+ display: flex;
+ justify-content: space-between;
+ flex-basis: 100%;
+ font-weight: 500;
+}
+
diff --git a/client/performance-profiler/components/core-web-vitals-display/core-web-vitals-details.tsx b/client/performance-profiler/components/core-web-vitals-display/core-web-vitals-details.tsx
new file mode 100644
index 00000000000000..77cf07b086ccaf
--- /dev/null
+++ b/client/performance-profiler/components/core-web-vitals-display/core-web-vitals-details.tsx
@@ -0,0 +1,168 @@
+import { useTranslate } from 'i18n-calypso';
+import { Metrics, PerformanceMetricsHistory } from 'calypso/data/site-profiler/types';
+import {
+ metricsNames,
+ metricsTresholds,
+ mapThresholdsToStatus,
+ metricValuations,
+} from 'calypso/performance-profiler/utils/metrics';
+import HistoryChart from '../charts/history-chart';
+import { MetricScale } from '../metric-scale';
+import { StatusIndicator } from '../status-indicator';
+
+type CoreWebVitalsDetailsProps = Record< Metrics, number > & {
+ history: PerformanceMetricsHistory;
+ activeTab: Metrics | null;
+};
+
+export const CoreWebVitalsDetails: React.FC< CoreWebVitalsDetailsProps > = ( {
+ activeTab,
+ history,
+ ...metrics
+} ) => {
+ const translate = useTranslate();
+
+ if ( ! activeTab ) {
+ return null;
+ }
+
+ const { displayName } = metricsNames[ activeTab ];
+ const value = metrics[ activeTab ];
+ const valuation = mapThresholdsToStatus( activeTab, value );
+
+ const { good, needsImprovement } = metricsTresholds[ activeTab ];
+
+ const formatUnit = ( value: number ) => {
+ if ( [ 'lcp', 'fcp', 'ttfb' ].includes( activeTab ) ) {
+ return +( value / 1000 ).toFixed( 2 );
+ }
+ return value;
+ };
+
+ const displayUnit = () => {
+ if ( [ 'lcp', 'fcp', 'ttfb' ].includes( activeTab ) ) {
+ return translate( 's', { comment: 'Used for displaying a time range in seconds, eg. 1-2s' } );
+ }
+ if ( [ 'inp', 'tbt' ].includes( activeTab ) ) {
+ return translate( 'ms', {
+ comment: 'Used for displaying a range in milliseconds, eg. 100-200ms',
+ } );
+ }
+ return '';
+ };
+
+ let metricsData: number[] = history?.metrics[ activeTab ] ?? [];
+ let dates = history?.collection_period ?? [];
+
+ // last 8 weeks only
+ metricsData = metricsData.slice( -8 );
+ dates = dates.slice( -8 );
+
+ // the comparison is inverse here because the last value is the most recent
+ const positiveTendency = metricsData[ metricsData.length - 1 ] < metricsData[ 0 ];
+
+ const dataAvailable = metricsData.length > 0 && metricsData.some( ( item ) => item !== null );
+ const historicalData = metricsData.map( ( item, index ) => {
+ let formattedDate: unknown;
+ const date = dates[ index ];
+ if ( 'string' === typeof date ) {
+ formattedDate = date;
+ } else {
+ const { year, month, day } = date;
+ formattedDate = `${ year }-${ month }-${ day }`;
+ }
+
+ return {
+ date: formattedDate,
+ value: formatUnit( item ),
+ };
+ } );
+
+ return (
+
+
+
+ { metricValuations[ activeTab ][ valuation ] }
+
+
+
+
+
+
+
{ translate( 'Fast' ) }
+
+ { translate( '0–%(to)s%(unit)s', {
+ args: { to: formatUnit( good ), unit: displayUnit() },
+ comment: 'Displaying a time range, eg. 0-1s',
+ } ) }
+
+
+
+
+
+
+
{ translate( 'Moderate' ) }
+
+ { translate( '%(from)s–%(to)s%(unit)s', {
+ args: {
+ from: formatUnit( good ),
+ to: formatUnit( needsImprovement ),
+ unit: displayUnit(),
+ },
+ comment: 'Displaying a time range, eg. 2-3s',
+ } ) }
+
+
+
+
+
+
+
{ translate( 'Slow' ) }
+
+ { translate( '>%(from)s%(unit)s', {
+ args: {
+ from: formatUnit( needsImprovement ),
+ unit: displayUnit(),
+ },
+ comment: 'Displaying a time range, eg. >2s',
+ } ) }
+
+
+
+
+
+ { metricValuations[ activeTab ].heading }
+
+
+ { metricValuations[ activeTab ].aka }
+
+
+ { metricValuations[ activeTab ].explanation }
+
+ { translate( 'Learn more ↗' ) }
+
+
+
+ { dataAvailable && (
+
+ { positiveTendency
+ ? translate( '%s has improved over the past eight weeks', {
+ args: [ displayName ],
+ } )
+ : translate( '%s has declined over the past eight weeks', {
+ args: [ displayName ],
+ } ) }
+
+ ) }
+
+
+
+ );
+};
diff --git a/client/performance-profiler/components/core-web-vitals-display/index.tsx b/client/performance-profiler/components/core-web-vitals-display/index.tsx
index 763212995dcd8c..dadd83c44f9c47 100644
--- a/client/performance-profiler/components/core-web-vitals-display/index.tsx
+++ b/client/performance-profiler/components/core-web-vitals-display/index.tsx
@@ -1,16 +1,10 @@
-import { useTranslate } from 'i18n-calypso';
+import { useDesktopBreakpoint } from '@automattic/viewport-react';
import { useState } from 'react';
import { Metrics, PerformanceMetricsHistory } from 'calypso/data/site-profiler/types';
-import {
- metricsNames,
- metricsTresholds,
- mapThresholdsToStatus,
- metricValuations,
-} from 'calypso/performance-profiler/utils/metrics';
-import HistoryChart from '../charts/history-chart';
-import { MetricScale } from '../metric-scale';
+import { CoreWebVitalsAccordion } from '../core-web-vitals-accordion';
import { MetricTabBar } from '../metric-tab-bar';
-import { StatusIndicator } from '../status-indicator';
+import { CoreWebVitalsDetails } from './core-web-vitals-details';
+
import './style.scss';
type CoreWebVitalsDisplayProps = Record< Metrics, number > & {
@@ -18,155 +12,33 @@ type CoreWebVitalsDisplayProps = Record< Metrics, number > & {
};
export const CoreWebVitalsDisplay = ( props: CoreWebVitalsDisplayProps ) => {
- const translate = useTranslate();
- const [ activeTab, setActiveTab ] = useState< Metrics >( 'fcp' );
-
- const { displayName } = metricsNames[ activeTab as keyof typeof metricsNames ];
- const value = props[ activeTab ];
- const valuation = mapThresholdsToStatus( activeTab as keyof typeof metricsTresholds, value );
-
- const { good, needsImprovement } = metricsTresholds[ activeTab as keyof typeof metricsTresholds ];
- const formatUnit = ( value: number ) => {
- if ( [ 'lcp', 'fcp', 'ttfb' ].includes( activeTab ) ) {
- return +( value / 1000 ).toFixed( 2 );
- }
-
- return value;
- };
-
- const displayUnit = () => {
- if ( [ 'lcp', 'fcp', 'ttfb' ].includes( activeTab ) ) {
- return translate( 's', { comment: 'Used for displaying a time range in seconds, eg. 1-2s' } );
- }
-
- if ( [ 'inp', 'tbt' ].includes( activeTab ) ) {
- return translate( 'ms', {
- comment: 'Used for displaying a range in milliseconds, eg. 100-200ms',
- } );
- }
-
- return '';
- };
-
- const { history } = props;
- let metrics: number[] = history?.metrics[ activeTab ] ?? [];
- let dates = history?.collection_period ?? [];
-
- // last 8 weeks only
- metrics = metrics.slice( -8 );
- dates = dates.slice( -8 );
-
- // the comparison is inverse here because the last value is the most recent
- const positiveTendency = metrics[ metrics.length - 1 ] < metrics[ 0 ];
-
- const dataAvailable = metrics.length > 0 && metrics.some( ( item ) => item !== null );
- const historicalData = metrics.map( ( item, index ) => {
- let formattedDate: unknown;
- const date = dates[ index ];
- if ( 'string' === typeof date ) {
- formattedDate = date; // this is to ensure compability with reports before https://code.a8c.com/D159137
- } else {
- const { year, month, day } = date;
- formattedDate = `${ year }-${ month }-${ day }`;
- }
-
- return {
- date: formattedDate,
- value: formatUnit( item ),
- };
- } );
+ const defaultTab = 'fcp';
+ const [ activeTab, setActiveTab ] = useState< Metrics | null >( defaultTab );
+ const isDesktop = useDesktopBreakpoint();
return (
-
-
-
-
-
- { metricValuations[ activeTab ][ valuation ] }
-
-
-
-
-
-
-
{ translate( 'Fast' ) }
-
- { translate( '0–%(to)s%(unit)s', {
- args: { to: formatUnit( good ), unit: displayUnit() },
- comment: 'Displaying a time range, eg. 0-1s',
- } ) }
-
-
-
-
-
-
-
{ translate( 'Moderate' ) }
-
- { translate( '%(from)s–%(to)s%(unit)s', {
- args: {
- from: formatUnit( good ),
- to: formatUnit( needsImprovement ),
- unit: displayUnit(),
- },
- comment: 'Displaying a time range, eg. 2-3s',
- } ) }
-
-
-
-
-
-
-
{ translate( 'Slow' ) }
-
- { translate( '>%(from)s%(unit)s', {
- args: {
- from: formatUnit( needsImprovement ),
- unit: displayUnit(),
- },
- comment: 'Displaying a time range, eg. >2s',
- } ) }
-
-
-
-
-
- { metricValuations[ activeTab ].heading }
-
-
- { metricValuations[ activeTab ].aka }
-
-
- { metricValuations[ activeTab ].explanation }
-
-
- { translate( 'Learn more ↗' ) }
-
-
-
-
- { dataAvailable && (
-
- { positiveTendency
- ? translate( '%s has improved over the past eight weeks', {
- args: [ displayName ],
- } )
- : translate( '%s has declined over the past eight weeks', {
- args: [ displayName ],
- } ) }
-
- ) }
-
+ { isDesktop && (
+
+
+
+
+ ) }
+ { ! isDesktop && (
+
+
+
+
-
-
+ ) }
+ >
);
};
diff --git a/client/performance-profiler/components/core-web-vitals-display/style.scss b/client/performance-profiler/components/core-web-vitals-display/style.scss
index c9ef91affa0c3f..21c21aab8660e8 100644
--- a/client/performance-profiler/components/core-web-vitals-display/style.scss
+++ b/client/performance-profiler/components/core-web-vitals-display/style.scss
@@ -6,6 +6,7 @@ $blueberry-color: #3858e9;
.core-web-vitals-display {
display: flex;
flex-direction: column;
+ width: 100%;
}
.core-web-vitals-display__ranges {
@@ -43,15 +44,18 @@ $blueberry-color: #3858e9;
border: 1.5px solid var(--studio-gray-5);
/* stylelint-disable-next-line scales/radii */
border-radius: 0 0 6px 6px;
- border-top: none;
padding: 24px;
display: flex;
flex-direction: row;
column-gap: 32px;
min-height: 360px;
+ flex-wrap: wrap;
+ margin-top: -1.5px;
& > div {
- flex: 50%;
+ flex-grow: 1;
+ flex-basis: 0;
+ min-width: 300px;
}
& > p {
diff --git a/client/performance-profiler/components/dashboard-content/index.tsx b/client/performance-profiler/components/dashboard-content/index.tsx
index 42681524832663..b326a689ccba23 100644
--- a/client/performance-profiler/components/dashboard-content/index.tsx
+++ b/client/performance-profiler/components/dashboard-content/index.tsx
@@ -13,11 +13,13 @@ import './style.scss';
type PerformanceProfilerDashboardContentProps = {
performanceReport: PerformanceReport;
url: string;
+ hash: string;
};
export const PerformanceProfilerDashboardContent = ( {
performanceReport,
url,
+ hash,
}: PerformanceProfilerDashboardContentProps ) => {
const { overall_score, fcp, lcp, cls, inp, ttfb, tbt, audits, history, screenshots, is_wpcom } =
performanceReport;
@@ -41,9 +43,11 @@ export const PerformanceProfilerDashboardContent = ( {
tbt={ tbt }
history={ history }
/>
-
+
- { audits &&
}
+ { audits && (
+
+ ) }
diff --git a/client/performance-profiler/components/disclaimer-section/style.scss b/client/performance-profiler/components/disclaimer-section/style.scss
index dbd0019453d433..6de50c637f4f06 100644
--- a/client/performance-profiler/components/disclaimer-section/style.scss
+++ b/client/performance-profiler/components/disclaimer-section/style.scss
@@ -1,16 +1,20 @@
.performance-profiler-disclaimer {
display: flex;
padding: 64px 0;
- justify-content: center;
+ justify-content: space-between;
align-items: flex-end;
gap: 10px;
+ flex-wrap: wrap;
color: var(--studio-gray-70);
font-size: $font-body-extra-small;
line-height: $font-title-small;
+ .content {
+ max-width: 600px;
+ }
+
.link {
- width: 470px;
flex-shrink: 0;
text-align: right;
diff --git a/client/performance-profiler/components/header/index.tsx b/client/performance-profiler/components/header/index.tsx
index 90d4b0e9b5a79d..931979204c9845 100644
--- a/client/performance-profiler/components/header/index.tsx
+++ b/client/performance-profiler/components/header/index.tsx
@@ -51,7 +51,9 @@ export const PerformanceProfilerHeader = ( props: HeaderProps ) => {
-
+
+
+
{ urlParts.hostname ?? '' }
@@ -69,7 +71,7 @@ export const PerformanceProfilerHeader = ( props: HeaderProps ) => {
{ showNavigationTabs && (
-
+
onTabChange( TabType.mobile ) }
selected={ activeTab === TabType.mobile }
diff --git a/client/performance-profiler/components/header/style.scss b/client/performance-profiler/components/header/style.scss
index 1e277322eaf964..751d977ac57fac 100644
--- a/client/performance-profiler/components/header/style.scss
+++ b/client/performance-profiler/components/header/style.scss
@@ -1,6 +1,12 @@
@import "@wordpress/base-styles/breakpoints";
@import "@automattic/typography/styles/variables";
+.is-group-performance-profiler.is-logged-in {
+ .profiler-header {
+ padding-top: 72px; // 40px + 32px (masterbar height)
+ }
+}
+
.profiler-header {
color: #fff;
padding-top: 40px;
@@ -15,6 +21,22 @@
padding-bottom: 20px;
}
+ .profiler-header__badge {
+ font-size: $root-font-size;
+ text-decoration: none;
+ color: var(--studio-white);
+ padding: 0;
+ font-family: $brand-serif;
+
+ &:hover {
+ color: var(--studio-white);
+ text-decoration: none;
+ }
+ &:focus {
+ box-shadow: none;
+ }
+ }
+
.profiler-header__site-url {
display: flex;
flex-direction: row;
@@ -80,6 +102,18 @@
box-shadow: none;
margin: 0;
+ .section-nav__mobile-header {
+ display: none;
+ }
+
+ .section-nav__panel {
+ display: flex;
+ align-items: center;
+ flex-wrap: nowrap;
+ overflow-wrap: normal;
+ justify-content: space-between;
+ }
+
.section-nav-tabs__list {
// Add padding to display the rounded border and remove the same distance to keep it aligned vertically
padding-left: 6px;
@@ -115,6 +149,7 @@
padding: 0 16px;
align-items: center;
gap: 10px;
+ width: auto;
span {
font-size: $font-body-small;
@@ -168,6 +203,7 @@
align-items: center;
gap: 16px;
font-size: $font-body-small;
+ margin-left: 25px;
.report-site-details {
display: flex;
diff --git a/client/performance-profiler/components/insights-section/index.tsx b/client/performance-profiler/components/insights-section/index.tsx
index 61ff87fa048e14..17fb63fd0cc5e3 100644
--- a/client/performance-profiler/components/insights-section/index.tsx
+++ b/client/performance-profiler/components/insights-section/index.tsx
@@ -7,11 +7,12 @@ type InsightsSectionProps = {
audits: Record< string, PerformanceMetricsItemQueryResponse >;
url: string;
isWpcom: boolean;
+ hash: string;
};
export const InsightsSection = ( props: InsightsSectionProps ) => {
const translate = useTranslate();
- const { audits, isWpcom } = props;
+ const { audits, isWpcom, hash } = props;
return (
@@ -26,6 +27,7 @@ export const InsightsSection = ( props: InsightsSectionProps ) => {
index={ index }
url={ props.url }
isWpcom={ isWpcom }
+ hash={ hash }
/>
) ) }
diff --git a/client/performance-profiler/components/insights-section/style.scss b/client/performance-profiler/components/insights-section/style.scss
index 00403671bf1900..fc0a0a59fb7adc 100644
--- a/client/performance-profiler/components/insights-section/style.scss
+++ b/client/performance-profiler/components/insights-section/style.scss
@@ -65,6 +65,10 @@ $blueberry-color: #3858e9;
color: var(--studio-orange-40);
}
}
+
+ .header-code {
+ color: #3858e9;
+ }
}
&.is-expanded .foldable-card__main {
@@ -118,6 +122,40 @@ $blueberry-color: #3858e9;
align-items: flex-start;
gap: 32px;
align-self: stretch;
+
+ p {
+ line-height: 24px;
+ }
+ }
+
+ .generated-with-ia {
+ font-weight: 500;
+ }
+
+ .metrics-insight-content {
+ .survey {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ flex-wrap: wrap;
+ }
+
+ .options {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ cursor: pointer;
+
+ &.good {
+ color: var(--studio-green-50);
+ fill: var(--studio-green-50);
+ }
+
+ &.bad {
+ color: var(--studio-red-50);
+ fill: var(--studio-red-50);
+ }
+ }
}
.metrics-insight-detailed-content {
diff --git a/client/performance-profiler/components/llm-message/index.tsx b/client/performance-profiler/components/llm-message/index.tsx
new file mode 100644
index 00000000000000..33514192517e82
--- /dev/null
+++ b/client/performance-profiler/components/llm-message/index.tsx
@@ -0,0 +1,30 @@
+import clsx from 'clsx';
+import { useTranslate } from 'i18n-calypso';
+import { ReactNode } from 'react';
+import IAIcon from 'calypso/assets/images/performance-profiler/ia-icon.svg';
+
+import './style.scss';
+
+interface LLMMessageProps {
+ message: string | ReactNode;
+ secondaryArea?: ReactNode;
+ rotate?: boolean;
+}
+
+export const LLMMessage = ( { message, rotate, secondaryArea }: LLMMessageProps ) => {
+ const translate = useTranslate();
+
+ return (
+
+
+
+
{ message }
+
+ { secondaryArea }
+
+ );
+};
diff --git a/client/performance-profiler/components/llm-message/style.scss b/client/performance-profiler/components/llm-message/style.scss
new file mode 100644
index 00000000000000..9457b135c568c5
--- /dev/null
+++ b/client/performance-profiler/components/llm-message/style.scss
@@ -0,0 +1,37 @@
+.performance-profiler-llm-message {
+ box-sizing: border-box;
+ display: flex;
+ padding: 16px;
+ align-items: center;
+ align-self: stretch;
+ justify-content: space-between;
+ background: linear-gradient(0deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.95) 100%), linear-gradient(90deg, #4458e4 0%, #069e08 100%);
+ /* stylelint-disable-next-line scales/radii */
+ border-radius: 6px;
+
+ color: #000;
+ font-family: "SF Pro Text", $sans;
+ font-size: $font-body-small;
+ line-height: 20px;
+ flex-wrap: wrap;
+ gap: 10px;
+
+ .content {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ .rotate {
+ animation: rotation 3s infinite linear;
+ }
+
+ @keyframes rotation {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+ }
+}
diff --git a/client/performance-profiler/components/message-display/index.tsx b/client/performance-profiler/components/message-display/index.tsx
new file mode 100644
index 00000000000000..3b04fb9ee7580e
--- /dev/null
+++ b/client/performance-profiler/components/message-display/index.tsx
@@ -0,0 +1,55 @@
+import { Gridicon } from '@automattic/components';
+import { Button, IconType } from '@wordpress/components';
+import clsx from 'clsx';
+import { ReactNode } from 'react';
+import { Badge } from 'calypso/performance-profiler/components/badge';
+
+import './style.scss';
+
+type Props = {
+ title?: string;
+ message: string | ReactNode;
+ ctaText?: string;
+ ctaHref?: string;
+ secondaryMessage?: string;
+ displayBadge?: boolean;
+ ctaIcon?: string;
+ isErrorMessage?: boolean;
+};
+
+export const MessageDisplay = ( {
+ displayBadge = false,
+ title,
+ message,
+ ctaText,
+ ctaHref,
+ secondaryMessage,
+ ctaIcon = '',
+ isErrorMessage = false,
+}: Props ) => {
+ return (
+
+
+
+ { displayBadge &&
}
+
+ { isErrorMessage &&
}
+ { title &&
{ title } }
+
{ message }
+ { ctaText && ctaHref && (
+
+ { ctaText }
+
+ ) }
+
+ { secondaryMessage &&
{ secondaryMessage }
}
+
+
+
+ );
+};
diff --git a/client/performance-profiler/components/message-display/style.scss b/client/performance-profiler/components/message-display/style.scss
new file mode 100644
index 00000000000000..2c714d930c4ba6
--- /dev/null
+++ b/client/performance-profiler/components/message-display/style.scss
@@ -0,0 +1,105 @@
+@import "@wordpress/base-styles/breakpoints";
+@import "@automattic/typography/styles/variables";
+
+$blueberry-color: #3858e9;
+
+.message-display {
+ color: #fff;
+ padding-top: 40px;
+ background: linear-gradient(180deg, var(--studio-gray-100) 25.44%, rgba(16, 21, 23, 0) 100%), url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGNpcmNsZSBjeD0iOCIgY3k9IjgiIHI9IjEiIGZpbGw9IndoaXRlIiBmaWxsLW9wYWNpdHk9IjAuMjUiLz4KPC9zdmc+Cg==) repeat, var(--studio-gray-100);
+
+ a {
+ color: $blueberry-color;
+
+ &:hover {
+ color: darken($blueberry-color, 10%);
+ text-decoration: underline;
+ }
+
+ &.is-primary {
+ color: #fff;
+ background-color: $blueberry-color;
+ border-radius: 4px;
+
+ &:hover:not(:disabled),
+ &:active:not(:disabled),
+ &:focus:not(:disabled) {
+ background-color: darken($blueberry-color, 10%);
+ border-color: darken($blueberry-color, 10%);
+ box-shadow: none;
+ }
+ }
+ }
+
+ p {
+ margin: 0;
+ }
+
+ .l-block-wrapper {
+ height: 100%;
+ max-width: 1056px;
+ }
+
+ .message-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+ gap: 64px;
+ min-height: 700px;
+ width: 50%;
+
+ .main-message {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+ gap: 16px;
+
+ &.error {
+ border-radius: 4px;
+ border: 1px solid var(--studio-red-70);
+ background-color: var(--studio-red-70);
+ padding: 16px;
+ padding-left: 52px;
+ position: relative;
+
+ .gridicon {
+ position: absolute;
+ top: 16px;
+ left: 16px;
+ }
+
+ .cta-button {
+ margin: 0;
+ }
+ }
+
+ .title {
+ font-family: $brand-serif;
+ font-size: $font-headline-medium;
+ line-height: $font-headline-large;
+ color: #fff;
+ }
+
+ .message {
+ font-size: $font-body;
+ font-weight: 500;
+ line-height: $font-title-medium;
+ }
+
+ .cta-button {
+ margin-top: 16px;
+ font-size: $font-body-small;
+ line-height: $font-title-small;
+ padding: 20px;
+ }
+ }
+
+ .secondary-message {
+ font-size: $font-body-small;
+ line-height: $font-title-small;
+ color: var(--studio-gray-20);
+ }
+ }
+}
diff --git a/client/performance-profiler/components/metric-tab-bar/style.scss b/client/performance-profiler/components/metric-tab-bar/style.scss
index 6ba4b44da50760..540517d439f3d7 100644
--- a/client/performance-profiler/components/metric-tab-bar/style.scss
+++ b/client/performance-profiler/components/metric-tab-bar/style.scss
@@ -13,8 +13,8 @@ $blueberry-color: #3858e9;
display: flex;
gap: 6px;
padding: 12px 16px;
+ background: var(--studio-white);
border: 1.5px solid var(--studio-white);
- border-bottom: 1.5px solid var(--studio-gray-5);
text-align: initial;
flex-grow: 1;
diff --git a/client/performance-profiler/components/metrics-insight/index.tsx b/client/performance-profiler/components/metrics-insight/index.tsx
index 737dee0341104d..6ba2e8fbc0e927 100644
--- a/client/performance-profiler/components/metrics-insight/index.tsx
+++ b/client/performance-profiler/components/metrics-insight/index.tsx
@@ -16,6 +16,7 @@ interface MetricsInsightProps {
index: number;
url?: string;
isWpcom: boolean;
+ hash: string;
}
const Card = styled( FoldableCard )`
@@ -53,21 +54,25 @@ const Header = styled.div`
font-size: 16px;
font-weight: 500;
margin-right: 8px;
+ width: 15px;
+ text-align: right;
}
`;
const Content = styled.div`
- padding: 24px;
+ padding: 15px 22px;
`;
export const MetricsInsight: React.FC< MetricsInsightProps > = ( props ) => {
const translate = useTranslate();
- const { insight, onClick, index, isWpcom } = props;
+ const { insight, onClick, index, isWpcom, hash } = props;
const [ retrieveInsight, setRetrieveInsight ] = useState( false );
const { data: llmAnswer, isLoading: isLoadingLlmAnswer } = useSupportChatLLMQuery(
insight.description ?? '',
+ hash,
+ isWpcom,
isEnabled( 'performance-profiler/llm' ) && retrieveInsight
);
const tip = tips[ insight.id ];
@@ -103,6 +108,7 @@ export const MetricsInsight: React.FC< MetricsInsightProps > = ( props ) => {
} }
secondaryArea={ tip && }
isLoading={ isEnabled( 'performance-profiler/llm' ) && isLoadingLlmAnswer }
+ IAGenerated={ isEnabled( 'performance-profiler/llm' ) }
/>
diff --git a/client/performance-profiler/components/metrics-insight/insight-content.tsx b/client/performance-profiler/components/metrics-insight/insight-content.tsx
index 2261deed506336..756f89a4bf8536 100644
--- a/client/performance-profiler/components/metrics-insight/insight-content.tsx
+++ b/client/performance-profiler/components/metrics-insight/insight-content.tsx
@@ -1,23 +1,37 @@
import { useTranslate } from 'i18n-calypso';
+import { useState } from 'react';
import Markdown from 'react-markdown';
import { PerformanceMetricsItemQueryResponse } from 'calypso/data/site-profiler/types';
+import { recordTracksEvent } from 'calypso/lib/analytics/tracks';
+import { LLMMessage } from 'calypso/performance-profiler/components/llm-message';
+import { ThumbsUpIcon, ThumbsDownIcon } from 'calypso/performance-profiler/icons/thumbs';
import { InsightDetailedContent } from './insight-detailed-content';
interface InsightContentProps {
data: PerformanceMetricsItemQueryResponse;
secondaryArea?: React.ReactNode;
isLoading?: boolean;
+ IAGenerated: boolean;
}
export const InsightContent: React.FC< InsightContentProps > = ( props ) => {
const translate = useTranslate();
- const { data, isLoading } = props;
+ const { data, isLoading, IAGenerated } = props;
const { description = '' } = data ?? {};
+ const [ feedbackSent, setFeedbackSent ] = useState( false );
+ const onSurveyClick = ( rating: string ) => {
+ recordTracksEvent( 'calypso_performance_profiler_llm_survey_click', {
+ rating,
+ description,
+ } );
+
+ setFeedbackSent( true );
+ };
return (
{ isLoading ? (
- translate( 'Looking for the best solution…' )
+
) : (
<>
@@ -34,6 +48,47 @@ export const InsightContent: React.FC< InsightContentProps > = ( props ) => {
{ props.secondaryArea }
+
+ { IAGenerated && (
+ { translate( 'Generated with IA' ) }
+ }
+ secondaryArea={
+
+ { feedbackSent ? (
+ translate( 'Thanks for the feedback!' )
+ ) : (
+ <>
+
{ translate( 'How did we do?' ) }
+
onSurveyClick( 'good' ) }
+ onKeyUp={ () => onSurveyClick( 'good' ) }
+ role="button"
+ tabIndex={ 0 }
+ >
+
+
+ { translate( "Good, it's helpful" ) }
+
+
onSurveyClick( 'bad' ) }
+ onKeyUp={ () => onSurveyClick( 'bad' ) }
+ role="button"
+ tabIndex={ 0 }
+ >
+
+ { translate( 'Not helpful' ) }
+
+ >
+ ) }
+
+ }
+ />
+ ) }
+
{ data.details?.type && (
diff --git a/client/performance-profiler/components/metrics-insight/insight-header.tsx b/client/performance-profiler/components/metrics-insight/insight-header.tsx
index 899831aade18d9..37941d36e1131b 100644
--- a/client/performance-profiler/components/metrics-insight/insight-header.tsx
+++ b/client/performance-profiler/components/metrics-insight/insight-header.tsx
@@ -23,7 +23,7 @@ export const InsightHeader: React.FC< InsightHeaderProps > = ( props ) => {
return
{ props.children }
;
},
code( props ) {
- return
{ props.children } ;
+ return
{ props.children } ;
},
} }
>
diff --git a/client/performance-profiler/components/metrics-insight/insight-table.tsx b/client/performance-profiler/components/metrics-insight/insight-table.tsx
index 857234bfe341f8..3332afa3015a9f 100644
--- a/client/performance-profiler/components/metrics-insight/insight-table.tsx
+++ b/client/performance-profiler/components/metrics-insight/insight-table.tsx
@@ -73,9 +73,7 @@ function Cell( {
return (
{ data?.nodeLabel }
-
- { data?.snippet }
-
+
{ data?.snippet }
);
diff --git a/client/performance-profiler/components/migration-banner/index.tsx b/client/performance-profiler/components/migration-banner/index.tsx
index 680a5af80c1c91..1f76cf772654d8 100644
--- a/client/performance-profiler/components/migration-banner/index.tsx
+++ b/client/performance-profiler/components/migration-banner/index.tsx
@@ -1,4 +1,5 @@
import { Gridicon } from '@automattic/components';
+import { useDesktopBreakpoint } from '@automattic/viewport-react';
import { Button } from '@wordpress/components';
import { useTranslate } from 'i18n-calypso';
import MigrationBannerImg from 'calypso/assets/images/performance-profiler/migration-banner-img.png';
@@ -8,6 +9,7 @@ import './style.scss';
export const MigrationBanner = ( props: { url: string } ) => {
const translate = useTranslate();
+ const isDesktop = useDesktopBreakpoint();
return (
@@ -62,15 +64,17 @@ export const MigrationBanner = ( props: { url: string } ) => {
-
-
-
+ { isDesktop && (
+
+
+
+ ) }
{ translate( 'Trusted by 160 million worldwide' ) }
diff --git a/client/performance-profiler/components/newsletter-banner.tsx b/client/performance-profiler/components/newsletter-banner.tsx
index 5bd635ccf77c1b..c4dd01d6ec5afb 100644
--- a/client/performance-profiler/components/newsletter-banner.tsx
+++ b/client/performance-profiler/components/newsletter-banner.tsx
@@ -1,18 +1,30 @@
import styled from '@emotion/styled';
import { Button } from '@wordpress/components';
import { useTranslate } from 'i18n-calypso';
+import { useSelector } from 'calypso/state';
+import { isUserLoggedIn } from 'calypso/state/current-user/selectors';
const Container = styled.div`
- background-color: #f7f8fe;
+ background:
+ linear-gradient( 90deg, var( --studio-gray-100 ) 50%, rgba( 16, 21, 23, 0 ) 100% ),
+ fixed 10px 10px /16px 16px radial-gradient( var( --studio-gray-50 ) 1px, transparent 0 ),
+ var( --studio-gray-100 );
+ border-radius: 6px;
display: flex;
flex-direction: row;
align-items: center;
- gap: 24px;
+ gap: 10px;
justify-content: space-between;
width: 100%;
+ box-sizing: border-box;
+ flex-wrap: wrap;
+ padding: 24px;
- & > * {
- margin: 24px;
+ @media ( max-width: 600px ) {
+ background:
+ linear-gradient( 180deg, var( --studio-gray-100 ) 50%, rgba( 16, 21, 23, 0 ) 100% ),
+ fixed 10px 10px /16px 16px radial-gradient( var( --studio-gray-50 ) 1px, transparent 0 ),
+ var( --studio-gray-100 );
}
`;
@@ -20,10 +32,12 @@ const Heading = styled.div`
font-weight: 500;
line-height: 24px;
text-align: left;
+ color: var( --studio-white );
`;
const Body = styled.div`
- color: var( --studio-gray-70 );
+ color: var( --studio-gray-20 );
+ text-wrap: balance;
`;
const BlueberryButton = styled( Button )`
@@ -31,25 +45,42 @@ const BlueberryButton = styled( Button )`
&& {
background: #3858e9;
border-color: #3858e9;
+
+ &:hover:not( :disabled ),
+ &:active:not( :disabled ),
+ &:focus:not( :disabled ) {
+ background-color: darken( #3858e9, 10% );
+ border-color: darken( #3858e9, 10% );
+ box-shadow: none;
+ }
}
`;
-export const NewsletterBanner = () => {
+export const NewsletterBanner = ( { link }: { link: string } ) => {
const translate = useTranslate();
+ const isLoggedIn = useSelector( isUserLoggedIn );
return (
- { translate( 'Sign up for weekly performance reports—it’s free!' ) }
+
+ { translate( 'Get notified about changes to your site’s performance—it’s free!' ) }
+
{ translate(
- 'Monitor your site’s key performance metrics with a free report delivered to your inbox each week.'
+ "Monitor your site's key performance metrics with a free report delivered to your inbox each week."
) }
- { translate( 'All you need is a free WordPress.com account to get started.' ) }
+ { ! isLoggedIn && (
+
+ { translate( 'All you need is a free WordPress.com account to get started.' ) }
+
+ ) }
-
- { translate( 'Sign up for email reports' ) }
+
+ { isLoggedIn
+ ? translate( 'Enable email alerts' )
+ : translate( 'Sign up for email reports' ) }
);
diff --git a/client/performance-profiler/components/screenshot-timeline.tsx b/client/performance-profiler/components/screenshot-timeline.tsx
index 5114c8b53f63d3..3070e594d90137 100644
--- a/client/performance-profiler/components/screenshot-timeline.tsx
+++ b/client/performance-profiler/components/screenshot-timeline.tsx
@@ -2,11 +2,17 @@ import styled from '@emotion/styled';
import { translate } from 'i18n-calypso';
import { ScreenShotsTimeLine } from 'calypso/data/site-profiler/types';
+const Container = styled.div`
+ max-width: 100%;
+`;
+
const Timeline = styled.div`
display: flex;
flex-direction: row;
gap: 1.5rem;
text-align: center;
+ overflow: auto;
+ padding: 0 2px;
`;
const H2 = styled.h2`
@@ -17,6 +23,8 @@ const H2 = styled.h2`
const Thumbnail = styled.img`
border: 1px solid var( --studio-gray-0 );
border-radius: 6px;
+ width: 100%;
+ min-width: 60px;
`;
type Props = { screenshots: ScreenShotsTimeLine[] };
@@ -27,9 +35,9 @@ export const ScreenshotTimeline = ( { screenshots }: Props ) => {
}
return (
-
-
Timeline
-
{ translate( 'Screenshots of your site loading taken while loading the page.' ) }
+
+ { translate( 'Timeline' ) }
+ { translate( 'How your site appears to users while loading.' ) }
{ screenshots.map( ( screenshot, index ) => {
const timing = `${ ( screenshot.timing / 1000 ).toFixed( 1 ) }s`;
@@ -41,6 +49,6 @@ export const ScreenshotTimeline = ( { screenshots }: Props ) => {
);
} ) }
-
+
);
};
diff --git a/client/performance-profiler/components/tip/style.scss b/client/performance-profiler/components/tip/style.scss
index 020cba5dcde367..87f047f8ca2eb3 100644
--- a/client/performance-profiler/components/tip/style.scss
+++ b/client/performance-profiler/components/tip/style.scss
@@ -1,8 +1,10 @@
.performance-profiler-tip {
max-width: 460px;
- background-color: #e7f0fa;
+ background-color: #f7f8fe;
padding: 25px;
min-width: 300px;
+ /* stylelint-disable-next-line scales/radii */
+ border-radius: 6px;
h4 {
font-size: $font-body-small;
diff --git a/client/performance-profiler/controller.tsx b/client/performance-profiler/controller.tsx
index e9378d65770cd6..fd3e5622ec5c00 100644
--- a/client/performance-profiler/controller.tsx
+++ b/client/performance-profiler/controller.tsx
@@ -7,6 +7,7 @@ import Main from 'calypso/components/main';
import { isUserLoggedIn } from 'calypso/state/current-user/selectors';
import { TabType } from './components/header';
import { PerformanceProfilerDashboard } from './pages/dashboard';
+import { WeeklyReport } from './pages/weekly-report';
export function PerformanceProfilerDashboardContext( context: Context, next: () => void ): void {
const isLoggedIn = isUserLoggedIn( context.store.getState() );
@@ -41,6 +42,36 @@ export function PerformanceProfilerDashboardContext( context: Context, next: ()
next();
}
+export function WeeklyReportContext( context: Context, next: () => void ): void {
+ const isLoggedIn = isUserLoggedIn( context.store.getState() );
+
+ if ( ! config.isEnabled( 'performance-profiler' ) ) {
+ page.redirect( '/' );
+ return;
+ }
+
+ if ( ! isLoggedIn ) {
+ window.location.href = '/log-in?redirect_to=' + encodeURIComponent( context.path );
+ return;
+ }
+
+ const url = context.query?.url?.startsWith( 'http' )
+ ? context.query.url
+ : `https://${ context.query?.url ?? '' }`;
+
+ context.primary = (
+ <>
+
+
+
+
+
+ >
+ );
+
+ next();
+}
+
export const notFound = ( context: Context, next: () => void ) => {
context.primary = (
{
+export const useSupportChatLLMQuery = (
+ description: string,
+ hash: string,
+ is_wpcom: boolean,
+ enable: boolean
+) => {
const question = `I need to fix the following issue to improve the performance of site: ${ description }.`;
const howToAnswer =
'Answer me in two topics in bold: "Why is this important?" and "How to fix this?"';
@@ -13,14 +18,14 @@ export const useSupportChatLLMQuery = ( description: string, enable: boolean ) =
return useQuery( {
// eslint-disable-next-line @tanstack/query/exhaustive-deps
- queryKey: [ 'support', 'chat', description ],
+ queryKey: [ 'support', 'chat', description, is_wpcom ],
queryFn: () =>
wp.req.post(
{
- path: '/odie/chat/wpcom-support-chat/',
+ path: `/odie/assistant/performance-profiler?hash=${ hash }`,
apiNamespace: 'wpcom/v2',
},
- { message }
+ { message, is_wpcom }
),
meta: {
persist: false,
diff --git a/client/performance-profiler/icons/thumbs.tsx b/client/performance-profiler/icons/thumbs.tsx
new file mode 100644
index 00000000000000..123339a05dfd2e
--- /dev/null
+++ b/client/performance-profiler/icons/thumbs.tsx
@@ -0,0 +1,33 @@
+import React from 'react';
+
+export const ThumbsUpIcon = ( { className }: { className?: string } ) => (
+
+
+
+);
+
+export const ThumbsDownIcon = ( { className }: { className?: string } ) => (
+
+
+
+);
diff --git a/client/performance-profiler/index.web.ts b/client/performance-profiler/index.web.ts
index 05deecca75f4be..35f356daa045ea 100644
--- a/client/performance-profiler/index.web.ts
+++ b/client/performance-profiler/index.web.ts
@@ -1,8 +1,9 @@
import page from '@automattic/calypso-router';
import { makeLayout, render as clientRender } from 'calypso/controller/index.web';
-import { PerformanceProfilerDashboardContext, notFound } from './controller';
+import { PerformanceProfilerDashboardContext, WeeklyReportContext, notFound } from './controller';
export default function () {
page( '/speed-test-tool/', PerformanceProfilerDashboardContext, makeLayout, clientRender );
+ page( '/speed-test-tool/weekly-report', WeeklyReportContext, makeLayout, clientRender );
page( '/speed-test-tool*', notFound, makeLayout, clientRender );
}
diff --git a/client/performance-profiler/pages/dashboard/index.tsx b/client/performance-profiler/pages/dashboard/index.tsx
index 199ae8f9387ae4..d8e2b1153c1e0a 100644
--- a/client/performance-profiler/pages/dashboard/index.tsx
+++ b/client/performance-profiler/pages/dashboard/index.tsx
@@ -101,6 +101,7 @@ export const PerformanceProfilerDashboard = ( props: PerformanceProfilerDashboar
) }
diff --git a/client/performance-profiler/pages/weekly-report/index.tsx b/client/performance-profiler/pages/weekly-report/index.tsx
new file mode 100644
index 00000000000000..aa6583c802444b
--- /dev/null
+++ b/client/performance-profiler/pages/weekly-report/index.tsx
@@ -0,0 +1,141 @@
+import styled from '@emotion/styled';
+import { useTranslate } from 'i18n-calypso';
+import { useEffect } from 'react';
+import DocumentHead from 'calypso/components/data/document-head';
+import { useLeadMutation } from 'calypso/data/site-profiler/use-lead-query';
+import { MessageDisplay } from 'calypso/performance-profiler/components/message-display';
+
+type WeeklyReportProps = {
+ url: string;
+ hash: string;
+};
+
+const LoaderText = styled.span`
+ display: flex;
+ align-items: center;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 24px;
+ position: relative;
+
+ &:before {
+ content: '';
+ display: inline-block;
+ border-radius: 50%;
+ margin-right: 10px;
+ content: '';
+ width: 16px;
+ height: 16px;
+ border: solid 2px #074ee8;
+ border-radius: 50%;
+ border-bottom-color: transparent;
+ -webkit-transition: all 0.5s ease-in;
+ -webkit-animation-name: rotate;
+ -webkit-animation-duration: 1s;
+ -webkit-animation-iteration-count: infinite;
+ -webkit-animation-timing-function: linear;
+
+ transition: all 0.5s ease-in;
+ animation-name: rotate;
+ animation-duration: 1s;
+ animation-iteration-count: infinite;
+ animation-timing-function: linear;
+ }
+
+ @keyframes rotate {
+ from {
+ transform: rotate( 0deg );
+ }
+ to {
+ transform: rotate( 360deg );
+ }
+ }
+
+ @-webkit-keyframes rotate {
+ from {
+ -webkit-transform: rotate( 0deg );
+ }
+ to {
+ -webkit-transform: rotate( 360deg );
+ }
+ }
+`;
+
+const ErrorSecondLine = styled.span`
+ color: var( --studio-red-5 );
+ font-weight: 400;
+ line-height: 20px;
+`;
+
+export const WeeklyReport = ( props: WeeklyReportProps ) => {
+ const translate = useTranslate();
+ const { url, hash } = props;
+
+ const siteUrl = new URL( url );
+
+ const { mutate, isPending, isError, isSuccess } = useLeadMutation( url, hash );
+
+ useEffect( () => {
+ mutate();
+ }, [ mutate ] );
+
+ const secondaryMessage = translate(
+ 'You can stop receiving performance reports at any time by clicking the Unsubscribe link in the email footer.'
+ );
+
+ return (
+
+
+
+ { isPending && (
+
+ { translate( 'Enabling email reports for %s', {
+ args: [ siteUrl.host ],
+ } ) }
+
+ }
+ secondaryMessage={ secondaryMessage }
+ />
+ ) }
+ { isError && (
+
+ { translate( 'Email reports could not be enabled for %s', {
+ args: [ siteUrl.host ],
+ } ) }
+
+
+ { translate(
+ 'Please try again or contact support if you continue to experience problems.'
+ ) }
+
+ >
+ }
+ ctaText={ translate( 'Enable email reports' ) }
+ ctaHref={ `/speed-test-tool/weekly-report?url=${ url }&hash=${ hash }` }
+ secondaryMessage={ secondaryMessage }
+ />
+ ) }
+ { isSuccess && (
+ } }
+ ) }
+ ctaText={ translate( '← Back to speed test' ) }
+ ctaHref="/speed-test"
+ ctaIcon="arrow-left"
+ secondaryMessage={ secondaryMessage }
+ />
+ ) }
+
+ );
+};
diff --git a/client/reader/tags/controller.tsx b/client/reader/tags/controller.tsx
index 7fdbcd6e1df33b..6721d0ceec07a4 100644
--- a/client/reader/tags/controller.tsx
+++ b/client/reader/tags/controller.tsx
@@ -94,7 +94,7 @@ export const fetchAlphabeticTags = ( context: PageJSContext, next: ( e?: Error )
}
performanceMark( context as PartialContext, 'fetchAlphabeticTags' );
- const currentUserLocale = getCurrentUserLocale( context.store.getState() );
+ const currentUserLocale = getCurrentUserLocale( context.store.getState() ) || context.lang;
context.queryClient
.fetchQuery( {
@@ -102,7 +102,7 @@ export const fetchAlphabeticTags = ( context: PageJSContext, next: ( e?: Error )
queryFn: () => {
return wpcom.req.get( '/read/tags/alphabetic', {
apiVersion: '1.2',
- lang: currentUserLocale, // Note: undefined will be omitted by the query string builder.
+ locale: currentUserLocale, // Note: undefined will be omitted by the query string builder.
} );
},
staleTime: 86400000, // 24 hours
diff --git a/client/sections.js b/client/sections.js
index 275a171dd61a60..c2c771e588061e 100644
--- a/client/sections.js
+++ b/client/sections.js
@@ -252,15 +252,6 @@ const sections = [
enableLoggedOut: true,
isomorphic: true,
},
- {
- name: 'start-with',
- paths: [ '/start-with' ],
- module: 'calypso/start-with',
- enableLoggedOut: true,
- group: 'start-with',
- isomorphic: true,
- trackLoadPerformance: true,
- },
{
name: 'jetpack-app',
paths: [ '/jetpack-app' ],
diff --git a/client/signup/steps/domains/index.jsx b/client/signup/steps/domains/index.jsx
index 5351057ffca6c8..668b16d63e23a4 100644
--- a/client/signup/steps/domains/index.jsx
+++ b/client/signup/steps/domains/index.jsx
@@ -16,12 +16,14 @@ import PropTypes from 'prop-types';
import { parse } from 'qs';
import { Component } from 'react';
import { connect } from 'react-redux';
+import AsyncLoad from 'calypso/components/async-load';
import QueryProductsList from 'calypso/components/data/query-products-list';
import { useMyDomainInputMode as inputMode } from 'calypso/components/domains/connect-domain-step/constants';
import RegisterDomainStep from 'calypso/components/domains/register-domain-step';
import { recordUseYourDomainButtonClick } from 'calypso/components/domains/register-domain-step/analytics';
import ReskinSideExplainer from 'calypso/components/domains/reskin-side-explainer';
import UseMyDomain from 'calypso/components/domains/use-my-domain';
+import FormattedHeader from 'calypso/components/formatted-header';
import Notice from 'calypso/components/notice';
import { SIGNUP_DOMAIN_ORIGIN } from 'calypso/lib/analytics/signup';
import {
@@ -45,7 +47,6 @@ import { getSitePropertyDefaults } from 'calypso/lib/signup/site-properties';
import CalypsoShoppingCartProvider from 'calypso/my-sites/checkout/calypso-shopping-cart-provider';
import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
import { domainManagementRoot } from 'calypso/my-sites/domains/paths';
-import StepWrapper from 'calypso/signup/step-wrapper';
import {
getStepUrl,
isPlanSelectionAvailableLaterInFlow,
@@ -359,8 +360,14 @@ export class RenderDomainsStep extends Component {
};
handleUseYourDomainClick = () => {
- page( this.getUseYourDomainUrl() );
+ // Stepper doesn't support page.js
+ const navigate = this.props.page || page;
this.props.recordUseYourDomainButtonClick( this.getAnalyticsSection() );
+ if ( this.props.useStepperWrapper ) {
+ this.props.goToNextStep( { navigateToUseMyDomain: true } );
+ } else {
+ navigate( this.getUseYourDomainUrl() );
+ }
};
handleDomainToDomainCart = async ( previousState ) => {
@@ -1329,6 +1336,7 @@ export class RenderDomainsStep extends Component {
isReskinned,
userSiteCount,
previousStepName,
+ useStepperWrapper,
} = this.props;
const siteUrl = this.props.selectedSite?.URL;
const siteSlug = this.props.queryObject?.siteSlug;
@@ -1337,8 +1345,17 @@ export class RenderDomainsStep extends Component {
let backLabelText;
let isExternalBackUrl = false;
- // Hide "Back" button in domains step if the user has no sites.
- const shouldHideBack = ! userSiteCount && previousStepName?.startsWith( 'user' );
+ /**
+ * Hide "Back" button in domains step if:
+ * 1. The user has no sites
+ * 2. This step was rendered immediately after account creation
+ * 3. The user is on the root domains step and not a child step section like use-your-domain
+ */
+ const shouldHideBack =
+ ! userSiteCount &&
+ previousStepName?.startsWith( 'user' ) &&
+ stepSectionName !== 'use-your-domain';
+
const hideBack = flowName === 'domain' || shouldHideBack;
const previousStepBackUrl = this.getPreviousStepUrl();
@@ -1399,8 +1416,42 @@ export class RenderDomainsStep extends Component {
const headerText = this.getHeaderText();
const fallbackSubHeaderText = this.getSubHeaderText();
+ if ( useStepperWrapper ) {
+ return (
+
+
+ { this.renderContent() }
+
+ }
+ formattedHeader={
+
+ }
+ backLabelText={ backLabelText }
+ hideSkip
+ align="center"
+ isWideLayout
+ />
+ );
+ }
+
return (
-
{
+export const submitDomainStepSelection = ( suggestion, section ) => {
let domainType = 'domain_reg';
if ( suggestion.is_free ) {
domainType = 'wpcom_subdomain';
@@ -1462,7 +1513,7 @@ const submitDomainStepSelection = ( suggestion, section ) => {
};
const RenderDomainsStepConnect = connect(
- ( state, { steps, flowName, stepName } ) => {
+ ( state, { steps, flowName, stepName, previousStepName } ) => {
const productsList = getAvailableProductsList( state );
const productsLoaded = ! isEmpty( productsList );
const isPlanStepSkipped = isPlanStepExistsAndSkipped( state );
@@ -1483,7 +1534,7 @@ const RenderDomainsStepConnect = connect(
[ 'pro', 'starter' ].includes( flowName ),
userLoggedIn,
multiDomainDefaultPlan,
- previousStepName: getPreviousStepName( flowName, stepName, userLoggedIn ),
+ previousStepName: previousStepName || getPreviousStepName( flowName, stepName, userLoggedIn ),
};
},
{
diff --git a/client/signup/steps/domains/style.scss b/client/signup/steps/domains/style.scss
index 0c46b7d87cb5f2..67f5f2b7fc8d35 100644
--- a/client/signup/steps/domains/style.scss
+++ b/client/signup/steps/domains/style.scss
@@ -15,6 +15,7 @@
}
}
+body.is-section-stepper .domains__step-content,
.is-section-signup .domains__step-content {
margin-bottom: 50px;
@@ -53,7 +54,6 @@
}
.search-component.is-open.has-focus {
- border: none;
border-radius: 2px;
box-shadow: 0 0 0 3px var(--color-accent-light);
}
@@ -236,6 +236,7 @@
* Styles for design reskin
* The `is-white-signup` class is attached to the body when the user is assigned the `reskinned` group of the `reskinSignupFlow` a/b test
*/
+body.is-section-stepper.is-group-stepper,
body.is-section-signup.is-white-signup {
$light-white: #f3f4f5;
@@ -456,6 +457,10 @@ body.is-section-signup.is-white-signup {
.signup__step.is-mailbox-domain {
padding: 0 12px;
}
+
+ .step-container__content .domains__step-content-domain-step {
+ padding: 0 12px;
+ }
}
// blue signup flow-specific styles
diff --git a/client/signup/style.scss b/client/signup/style.scss
index 742a23c5709add..ddeb6b7c00a1d3 100644
--- a/client/signup/style.scss
+++ b/client/signup/style.scss
@@ -368,8 +368,8 @@ body.is-section-signup .layout:not(.dops):not(.is-wccom-oauth-flow) .formatted-h
}
.social-buttons__button {
- border: 1px solid var(--studio-pink-50);
- color: var(--studio-pink-50);
+ border: 1px solid var(--studio-wordpress-blue);
+ color: var(--studio-wordpress-blue);
box-shadow: none;
max-width: 250px;
height: 48px;
@@ -393,6 +393,7 @@ body.is-section-signup .layout:not(.dops):not(.is-wccom-oauth-flow) .formatted-h
/**
* Common styles for reskinSignupFlow a/b test
*/
+body.is-section-stepper,
body.is-section-signup.is-white-signup .layout:not(.dops):not(.is-wccom-oauth-flow) {
$gray-100: #101517;
$gray-60: #50575e;
@@ -433,14 +434,6 @@ body.is-section-signup.is-white-signup .layout:not(.dops):not(.is-wccom-oauth-fl
}
}
- .navigation-link.button.is-borderless {
- color: var(--color-accent);
-
- svg {
- fill: var(--color-accent);
- }
- }
-
.step-wrapper__navigation {
.navigation-link.button.is-borderless {
color: $gray-100;
@@ -460,7 +453,11 @@ body.is-section-signup.is-white-signup .layout:not(.dops):not(.is-wccom-oauth-fl
}
}
- .signup:not(.is-onboarding-2023-pricing-grid) .step-wrapper:not(.is-horizontal-layout) {
+ .signup:not(.is-onboarding-2023-pricing-grid) .step-wrapper:not(.is-horizontal-layout),
+ // Stepper's header.
+ .step-container:not(.is-horizontal-layout) {
+ // Stepper's header.
+ .step-container__header,
.step-wrapper__header {
margin: 24px 20px;
diff --git a/client/signup/utils.js b/client/signup/utils.js
index ff4372bec857fc..0716f96e7f189d 100644
--- a/client/signup/utils.js
+++ b/client/signup/utils.js
@@ -47,12 +47,16 @@ export function getStepUrl( flowName, stepName, stepSectionName, localeSlug, par
const step = stepName ? `/${ stepName }` : '';
const section = stepSectionName ? `/${ stepSectionName }` : '';
const locale = localeSlug ? `/${ localeSlug }` : '';
+ const framework =
+ typeof window !== 'undefined' && window.location.pathname.startsWith( '/setup' )
+ ? '/setup'
+ : '/start';
const url =
- flowName === defaultFlowName
- ? // we don't include the default flow name in the route
- '/start' + step + section + locale
- : '/start' + flow + step + section + locale;
+ flowName === defaultFlowName && framework === '/start'
+ ? // we don't include the default flow name in the route in /start
+ framework + step + section + locale
+ : framework + flow + step + section + locale;
return addQueryArgs( params, url );
}
diff --git a/client/site-logs/components/logs-header/index.tsx b/client/site-logs/components/logs-header/index.tsx
index 6dc12718cd8bab..1922754d807999 100644
--- a/client/site-logs/components/logs-header/index.tsx
+++ b/client/site-logs/components/logs-header/index.tsx
@@ -37,7 +37,7 @@ export function LogsHeader( { logType }: { logType: string } ) {
/>
{ translate( 'Log type' ) }
-
+
{ options.map( ( option ) => {
return (
{
- recordTracksEvent( PLAN_RENEW_NAG_EVENT_NAMES.IN_VIEW, {
- is_site_owner: isSiteOwner,
- product_slug: productSlug,
- display_mode: 'grid',
- } );
- }, [ isSiteOwner, productSlug ] );
-
- const { ref } = useInView( {
- onChange: ( inView ) => inView && trackCallback(),
- } );
-
- return (
-
-
-
- {
- /* translators: %s - the plan's product name, such as Creator or Explorer. */
- sprintf( __( '%s Plan expired.' ), site.plan?.product_name_short )
- }
-
- { isSiteOwner && (
- {
- recordTracksEvent( PLAN_RENEW_NAG_EVENT_NAMES.ON_CLICK, {
- product_slug: productSlug,
- display_mode: 'grid',
- } );
- } }
- >
- { isUpgradeable ? __( 'Upgrade' ) : __( 'Renew' ) }
-
- ) }
-
-
- );
-}
diff --git a/client/sites-dashboard/components/sites-grid-item-select/README.md b/client/sites-dashboard/components/sites-grid-item-select/README.md
deleted file mode 100644
index 890adb4f0d3f1b..00000000000000
--- a/client/sites-dashboard/components/sites-grid-item-select/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# SitesGridItem
-
-Renders a SitesGridItem component with site selection option.
-
-## How to use
-
-```jsx
-import SitesGridItem from 'calypso/sites-dashboard/components/sites-grid-item';
-import type { SiteExcerptData } from '@automattic/sites';
-
-function render() {
- const site = {};
- return (
-
- {} } // optional
- >
-
- );
-}
-```
-
-## Props
-
-- `site`: a site data e.g. SiteExcerptData object.
-- `key`: unique key eg. Site ID.
-- `showLaunchNag`: boolean, optional, default: true.
-- `showBadgeSection`: boolean, optional, default: true.
-- `showThumbnailLink`: boolean, optional, default: true.
-- `showSiteRenewLink`: boolean, optional, default: true.
-- `onSiteSelectBtnClick`: function, optional, default: undefined.
diff --git a/client/sites-dashboard/components/sites-grid-item-select/docs/example.jsx b/client/sites-dashboard/components/sites-grid-item-select/docs/example.jsx
deleted file mode 100644
index 80a035939f38c1..00000000000000
--- a/client/sites-dashboard/components/sites-grid-item-select/docs/example.jsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { css } from '@emotion/css';
-import clsx from 'clsx';
-import { MEDIA_QUERIES } from 'calypso/sites-dashboard/utils';
-import sampleSiteData from '../../docs/sample-site-data';
-import { SitesGridItem } from '../../sites-grid-item';
-
-const container = css( {
- display: 'grid',
- gap: '32px',
-
- gridTemplateColumns: '1fr',
-
- [ MEDIA_QUERIES.mediumOrLarger ]: {
- gridTemplateColumns: 'repeat(2, 1fr)',
- },
-
- [ MEDIA_QUERIES.large ]: {
- gridTemplateColumns: 'repeat(3, 1fr)',
- },
-} );
-
-const className = css( {
- marginBlockStart: 0,
- marginInline: 0,
- marginBlockEnd: '1.5em',
-} );
-
-const itemClassName = css( {
- minWidth: 0,
-} );
-
-const SitesGridItemSelectExample = () => {
- return (
-
- { sampleSiteData.map( ( site ) => (
-
- {
- // console.log( _site );
- } }
- />
-
- ) ) }
-
- );
-};
-
-SitesGridItemSelectExample.displayName = 'SitesGridItemSelect';
-
-export default SitesGridItemSelectExample;
diff --git a/client/sites-dashboard/components/sites-grid-item.tsx b/client/sites-dashboard/components/sites-grid-item.tsx
index 662b7ac74ae80e..f6650756bbe021 100644
--- a/client/sites-dashboard/components/sites-grid-item.tsx
+++ b/client/sites-dashboard/components/sites-grid-item.tsx
@@ -1,32 +1,15 @@
import { Button } from '@automattic/components';
-import { useSiteLaunchStatusLabel, getSiteLaunchStatus } from '@automattic/sites';
import { css } from '@emotion/css';
import styled from '@emotion/styled';
import { useI18n } from '@wordpress/react-i18n';
import { memo } from 'react';
import { useInView } from 'react-intersection-observer';
-import SitesMigrationTrialBadge from 'calypso/sites-dashboard/components/sites-migration-trial-badge';
-import { useSelector } from 'calypso/state';
-import { isTrialSite } from 'calypso/state/sites/plans/selectors';
-import {
- displaySiteUrl,
- getDashboardUrl,
- isStagingSite,
- siteDefaultInterface,
- getSiteWpAdminUrl,
-} from '../utils';
-import { SitesEllipsisMenu } from './sites-ellipsis-menu';
-import { SitesGridActionRenew } from './sites-grid-action-renew';
+import { displaySiteUrl } from '../utils';
import { SitesGridTile } from './sites-grid-tile';
-import SitesLaunchStatusBadge from './sites-launch-status-badge';
-import SitesP2Badge from './sites-p2-badge';
import { SiteItemThumbnail } from './sites-site-item-thumbnail';
-import { SiteLaunchNag } from './sites-site-launch-nag';
import { SiteName } from './sites-site-name';
import { SiteUrl, Truncated } from './sites-site-url';
-import SitesStagingBadge from './sites-staging-badge';
import TransferNoticeWrapper from './sites-transfer-notice-wrapper';
-import { ThumbnailLink } from './thumbnail-link';
import { WithAtomicTransfer } from './with-atomic-transfer';
import type { SiteExcerptData } from '@automattic/sites';
@@ -45,13 +28,6 @@ const THUMBNAIL_DIMENSION = {
height: 401 / ASPECT_RATIO,
};
-const badges = css( {
- display: 'flex',
- gap: '8px',
- alignItems: 'center',
- marginInlineStart: 'auto',
-} );
-
const selectAction = css( {
display: 'flex',
gap: '8px',
@@ -76,61 +52,16 @@ const SitesGridItemSecondary = styled.div( {
justifyContent: 'space-between',
} );
-const EllipsisMenuContainer = styled.div( {
- width: '24px',
-} );
-
-const ellipsis = css( {
- '.button.ellipsis-menu__toggle': {
- padding: 0,
- },
-
- '.gridicon.ellipsis-menu__toggle-icon': {
- width: '24px',
- height: '16px',
- insetBlockStart: '4px',
- },
-} );
-
interface SitesGridItemProps {
site: SiteExcerptData;
- showLaunchNag?: boolean;
- showBadgeSection?: boolean;
- showThumbnailLink?: boolean;
- showSiteRenewLink?: boolean;
- onSiteSelectBtnClick?: ( site: SiteExcerptData ) => void;
+ onSiteSelectBtnClick: ( site: SiteExcerptData ) => void;
}
export const SitesGridItem = memo( ( props: SitesGridItemProps ) => {
const { __ } = useI18n();
- const {
- site,
- showLaunchNag = true,
- showBadgeSection = true,
- showThumbnailLink = true,
- showSiteRenewLink = true,
- onSiteSelectBtnClick,
- } = props;
-
- const isP2Site = site.options?.is_wpforteams_site;
- const isWpcomStagingSite = isStagingSite( site );
- const translatedStatus = useSiteLaunchStatusLabel( site );
- const isTrialSitePlan = useSelector( ( state ) => isTrialSite( state, site.ID ) );
- const wpAdminUrl = getSiteWpAdminUrl( site );
+ const { site, onSiteSelectBtnClick } = props;
const { ref, inView } = useInView( { triggerOnce: true } );
- const ThumbnailWrapper = showThumbnailLink ? ThumbnailLink : 'div';
-
- const siteDashboardUrlProps = showThumbnailLink
- ? {
- href:
- siteDefaultInterface( site ) === 'wp-admin'
- ? wpAdminUrl || getDashboardUrl( site.slug )
- : getDashboardUrl( site.slug ),
- title: __( 'Visit Dashboard' ),
- }
- : {};
-
let siteUrl = site.URL;
if ( site.options?.is_redirect && site.options?.unmapped_url ) {
siteUrl = site.options?.unmapped_url;
@@ -140,7 +71,7 @@ export const SitesGridItem = memo( ( props: SitesGridItemProps ) => {
ref={ ref }
leading={
<>
-
+
{
height={ THUMBNAIL_DIMENSION.height }
sizesAttr={ SIZES_ATTR }
/>
-
- { showSiteRenewLink && site.plan?.expired && (
-
- ) }
+
>
}
primary={
<>
-
- { site.title }
-
-
- { showBadgeSection && (
-
- { isP2Site && P2 }
- { isWpcomStagingSite && { __( 'Staging' ) } }
- { isTrialSitePlan && (
- { __( 'Trial' ) }
- ) }
- { getSiteLaunchStatus( site ) !== 'public' && ! isTrialSitePlan && (
- { translatedStatus }
- ) }
-
- { inView && }
-
-
- ) }
- { onSiteSelectBtnClick && (
-
- onSiteSelectBtnClick( site ) }>
- { __( 'Select this site' ) }
-
-
- ) }
+ { site.title }
+
+ onSiteSelectBtnClick( site ) }>
+ { __( 'Select this site' ) }
+
+
>
}
secondary={
@@ -195,8 +103,6 @@ export const SitesGridItem = memo( ( props: SitesGridItemProps ) => {
{ ( result ) => {
if ( result.wasTransferring ) {
return ;
- } else if ( showLaunchNag && 'unlaunched' === site.launch_status ) {
- return ;
}
return <>>;
} }
diff --git a/client/sites-dashboard/components/sites-grid-item/README.md b/client/sites-dashboard/components/sites-grid-item/README.md
index 9918e66c829179..81a9433375e1a6 100644
--- a/client/sites-dashboard/components/sites-grid-item/README.md
+++ b/client/sites-dashboard/components/sites-grid-item/README.md
@@ -1,11 +1,12 @@
# SitesGridItem
-Renders a SitesGridItem component.
+Renders a SitesGridItem component with site selection option.
## How to use
```jsx
import SitesGridItem from 'calypso/sites-dashboard/components/sites-grid-item';
+import type { SiteExcerptData } from '@automattic/sites';
function render() {
const site = {};
@@ -14,7 +15,8 @@ function render() {
+ onSiteSelectBtnClick={ ( s: SiteExcerptData ) => {} }
+ />
);
}
@@ -24,8 +26,4 @@ function render() {
- `site`: a site data e.g. SiteExcerptData object.
- `key`: unique key eg. Site ID.
-- `showLaunchNag`: boolean, optional, default: true.
-- `showBadgeSection`: boolean, optional, default: true.
-- `showThumbnailLink`: boolean, optional, default: true.
-- `showSiteRenewLink`: boolean, optional, default: true.
-- `onSiteSelectBtnClick`: function, optional, default: undefined.
+- `onSiteSelectBtnClick`: function.
diff --git a/client/sites-dashboard/components/sites-grid-item/docs/example.jsx b/client/sites-dashboard/components/sites-grid-item/docs/example.jsx
index f4af6ac558ddca..9cdad31acddd23 100644
--- a/client/sites-dashboard/components/sites-grid-item/docs/example.jsx
+++ b/client/sites-dashboard/components/sites-grid-item/docs/example.jsx
@@ -29,18 +29,24 @@ const itemClassName = css( {
minWidth: 0,
} );
-const SitesGridItemExample = () => {
+const SitesGridItemSelectExample = () => {
return (
{ sampleSiteData.map( ( site ) => (
-
+ {
+ console.log( _site );
+ } }
+ />
) ) }
);
};
-SitesGridItemExample.displayName = 'SitesGridItem';
+SitesGridItemSelectExample.displayName = 'SitesGridItemSelect';
-export default SitesGridItemExample;
+export default SitesGridItemSelectExample;
diff --git a/client/sites-dashboard/components/sites-grid.tsx b/client/sites-dashboard/components/sites-grid.tsx
index 3b5d063d85a1f9..c6223e4da734a8 100644
--- a/client/sites-dashboard/components/sites-grid.tsx
+++ b/client/sites-dashboard/components/sites-grid.tsx
@@ -26,21 +26,11 @@ interface SitesGridProps {
className?: string;
isLoading: boolean;
sites: SiteExcerptData[];
- siteSelectorMode?: boolean;
- onSiteSelectBtnClick?: ( site: SiteExcerptData ) => void;
+ onSiteSelectBtnClick: ( site: SiteExcerptData ) => void;
}
export const SitesGrid = ( props: SitesGridProps ) => {
- const { sites, isLoading, className, siteSelectorMode = false, onSiteSelectBtnClick } = props;
- const additionalProps = siteSelectorMode
- ? {
- showLaunchNag: false,
- showBadgeSection: false,
- showThumbnailLink: false,
- showSiteRenewLink: false,
- onSiteSelectBtnClick,
- }
- : {};
+ const { sites, isLoading, className, onSiteSelectBtnClick } = props;
return (
@@ -49,7 +39,11 @@ export const SitesGrid = ( props: SitesGridProps ) => {
.fill( null )
.map( ( _, i ) => )
: sites.map( ( site ) => (
-
+
) ) }
);
diff --git a/client/sites-dashboard/components/sites-plan-renew-nag.tsx b/client/sites-dashboard/components/sites-plan-renew-nag.tsx
index 0bc361577f6bd4..8f2561b7ea1237 100644
--- a/client/sites-dashboard/components/sites-plan-renew-nag.tsx
+++ b/client/sites-dashboard/components/sites-plan-renew-nag.tsx
@@ -22,12 +22,14 @@ const PlanRenewContainer = styled.div( {
marginTop: '-2px',
} );
-const PlanRenewLink = styled.a( {
- whiteSpace: 'nowrap',
- textDecoration: 'underline',
- fontSize: '12px',
- paddingTop: '2px',
-} );
+const PlanRenewLink = styled.a`
+ white-space: nowrap;
+ text-decoration: underline !important;
+ font-size: 12px;
+ font-weight: 400 !important;
+ padding-top: 2px;
+ color: var( --color-link ) !important;
+`;
const IconContainer = styled.div( {
color: '#ea303f',
diff --git a/client/sites-dashboard/components/sites-site-launch-nag.tsx b/client/sites-dashboard/components/sites-site-launch-nag.tsx
index cb477210416be4..05387929222fa3 100644
--- a/client/sites-dashboard/components/sites-site-launch-nag.tsx
+++ b/client/sites-dashboard/components/sites-site-launch-nag.tsx
@@ -37,6 +37,7 @@ const SiteLaunchNagLink = styled.a( {
fontSize: '12px',
lineHeight: '16px',
whiteSpace: 'nowrap',
+ color: 'var(--color-link) !important',
} );
const SiteLaunchNagText = styled.span( {
diff --git a/client/sites-dashboard/components/sites-site-plan.tsx b/client/sites-dashboard/components/sites-site-plan.tsx
index 7fff2ea43d6e04..79dcacbd3b80cd 100644
--- a/client/sites-dashboard/components/sites-site-plan.tsx
+++ b/client/sites-dashboard/components/sites-site-plan.tsx
@@ -6,6 +6,7 @@ import type { SiteExcerptData } from '@automattic/sites';
const SitePlanContainer = styled.div`
display: inline;
+ overflow: hidden;
> * {
vertical-align: middle;
line-height: normal;
diff --git a/client/sites-dashboard/components/test/__snapshots__/sites-grid-item.tsx.snap b/client/sites-dashboard/components/test/__snapshots__/sites-grid-item.tsx.snap
deleted file mode 100644
index d2ff9bc628b73a..00000000000000
--- a/client/sites-dashboard/components/test/__snapshots__/sites-grid-item.tsx.snap
+++ /dev/null
@@ -1,224 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[` Custom render 1`] = `
-
-`;
-
-exports[` Custom render 2 1`] = `
-
-`;
-
-exports[` Default render 1`] = `
-
-`;
diff --git a/client/sites-dashboard/components/test/sites-grid-item.tsx b/client/sites-dashboard/components/test/sites-grid-item.tsx
deleted file mode 100644
index 9a5d3417456d0d..00000000000000
--- a/client/sites-dashboard/components/test/sites-grid-item.tsx
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * @jest-environment jsdom
- */
-import React from 'react';
-import renderer from 'react-test-renderer';
-import { SitesGridItem } from 'calypso/sites-dashboard/components/sites-grid-item';
-import { useCheckSiteTransferStatus } from '../../hooks/use-check-site-transfer-status';
-import type { SiteExcerptData } from '@automattic/sites';
-
-function makeTestSite( { title = 'test', is_coming_soon = false, lang = 'en' } = {} ) {
- return {
- ID: 1,
- title,
- slug: 'test_slug',
- URL: '',
- launch_status: 'launched',
- options: {},
- jetpack: false,
- is_coming_soon,
- lang,
- };
-}
-
-jest.mock( 'calypso/sites-dashboard/hooks/use-check-site-transfer-status.tsx', () => ( {
- __esModule: true,
- useCheckSiteTransferStatus: jest.fn(),
-} ) );
-
-jest.mock( 'react-redux', () => ( {
- ...jest.requireActual( 'react-redux' ),
- useSelector: jest.fn(),
-} ) );
-
-describe( '', () => {
- beforeEach( () => {
- ( useCheckSiteTransferStatus as jest.Mock ).mockReturnValue( {
- isTransferInProgress: false,
- } );
- } );
- test( 'Default render', () => {
- const tree = renderer
- .create(
-
- )
- .toJSON();
- expect( tree ).toMatchSnapshot();
- } );
-
- test( 'Custom render', () => {
- const tree = renderer
- .create(
-
- )
- .toJSON();
- expect( tree ).toMatchSnapshot();
- } );
-
- test( 'Custom render 2', () => {
- const tree = renderer
- .create(
-
- )
- .toJSON();
- expect( tree ).toMatchSnapshot();
- } );
-} );
diff --git a/client/sites-dashboard/utils.ts b/client/sites-dashboard/utils.ts
index 771965c284074e..cece6fe30f8a69 100644
--- a/client/sites-dashboard/utils.ts
+++ b/client/sites-dashboard/utils.ts
@@ -106,10 +106,6 @@ export const generateSiteInterfaceLink = (
return targetLink;
};
-export const getSiteWpAdminUrl = ( site: SiteExcerptNetworkData ) => {
- return site?.options?.admin_url ?? '';
-};
-
export const SMALL_MEDIA_QUERY = 'screen and ( max-width: 600px )';
export const MEDIA_QUERIES = {
diff --git a/client/start-with/controller.tsx b/client/start-with/controller.tsx
deleted file mode 100644
index 2898d2f977e159..00000000000000
--- a/client/start-with/controller.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import config from '@automattic/calypso-config';
-import page, { Context } from '@automattic/calypso-router';
-import { translate } from 'i18n-calypso';
-import EmptyContent from 'calypso/components/empty-content';
-import Main from 'calypso/components/main';
-import { StartWithSquarePayments } from './square-payments';
-
-export function startWithSquarePaymentsContext( context: Context, next: () => void ): void {
- if ( ! config.isEnabled( 'start-with/square-payments' ) ) {
- page.redirect( '/' );
- return;
- }
-
- context.primary = (
-
-
-
- );
-
- next();
-}
-
-export const notFound = ( context: Context, next: () => void ) => {
- context.primary = (
-
- );
-
- next();
-};
diff --git a/client/start-with/index.node.ts b/client/start-with/index.node.ts
deleted file mode 100644
index d67ee135296e8b..00000000000000
--- a/client/start-with/index.node.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import { getLanguageRouteParam } from '@automattic/i18n-utils';
-import { makeLayout, setLocaleMiddleware } from 'calypso/controller';
-import { serverRouter } from 'calypso/server/isomorphic-routing';
-
-/**
- * Using the server routing for this section has the sole purpose of defining
- * a named route parameter for the language, that is used to set `context.lang`
- * via the `setLocaleMiddleware()`.
- *
- * The `context.lang` value is then used in the server renderer to properly
- * attach the translation files to the page.
- * @see https://github.com/Automattic/wp-calypso/blob/trunk/client/server/render/index.js#L171.
- */
-export default ( router: ReturnType< typeof serverRouter > ) => {
- const lang = getLanguageRouteParam();
-
- router( [ `/${ lang }/start-with(/*)?` ], setLocaleMiddleware(), makeLayout );
-};
diff --git a/client/start-with/index.web.ts b/client/start-with/index.web.ts
deleted file mode 100644
index 9f4c099115d284..00000000000000
--- a/client/start-with/index.web.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import page from '@automattic/calypso-router';
-import { makeLayout, render as clientRender } from 'calypso/controller/index.web';
-import { notFound, startWithSquarePaymentsContext } from 'calypso/start-with/controller';
-
-export default function () {
- page( '/start-with/square-payments', startWithSquarePaymentsContext, makeLayout, clientRender );
- page( '/start-with*', notFound, makeLayout, clientRender );
-}
diff --git a/client/start-with/package.json b/client/start-with/package.json
deleted file mode 100644
index 937fc8ce401241..00000000000000
--- a/client/start-with/package.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "main": "index.node.ts",
- "browser": "index.web.ts"
-}
diff --git a/client/start-with/square-payments/index.tsx b/client/start-with/square-payments/index.tsx
deleted file mode 100644
index b4089efa9f9704..00000000000000
--- a/client/start-with/square-payments/index.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { recordTracksEvent } from '@automattic/calypso-analytics';
-import page from '@automattic/calypso-router';
-import { Button } from '@automattic/components';
-import { useTranslate } from 'i18n-calypso';
-import React from 'react';
-import BloombergLogo from 'calypso/assets/images/start-with/Bloomberg.svg';
-import CNNLogo from 'calypso/assets/images/start-with/CNN.svg';
-import CondeNast from 'calypso/assets/images/start-with/Conde_Nast.svg';
-import DisneyLogo from 'calypso/assets/images/start-with/Disney.svg';
-import MetaLogo from 'calypso/assets/images/start-with/Meta.svg';
-import NewsCorpLogo from 'calypso/assets/images/start-with/News_Corp.svg';
-import SlackLogo from 'calypso/assets/images/start-with/Slack.svg';
-import TechCrunchImage from 'calypso/assets/images/start-with/Tech_Crunch.svg';
-import TimeLogo from 'calypso/assets/images/start-with/Time.svg';
-import USATodayImage from 'calypso/assets/images/start-with/USA_Today.svg';
-import DotcomWooSquareImage from 'calypso/assets/images/start-with/dotcom-woo-square.png';
-import DocumentHead from 'calypso/components/data/document-head';
-import PageViewTracker from 'calypso/lib/analytics/page-view-tracker';
-import './style.scss';
-
-export const StartWithSquarePayments: React.FC = () => {
- const translate = useTranslate();
- const onCTAClick = () => {
- recordTracksEvent( 'calypso_start_with_cta_click', { partner_bundle: 'square_payments' } );
- page( '/setup/entrepreneur/start?partnerBundle=square' );
- };
-
- return (
-
-
-
-
-
-
- { translate( 'Get Started with WordPress.com and Square Payments' ) }
-
-
- { translate(
- 'Partnering with Square Payments, WordPress.com offers you an easy way to build and manage your online store. Click below to begin your quick and easy setup process.'
- ) }
-
-
- { translate( 'Start your store now' ) }
-
-
-
-
-
-
-
-
- );
-};
diff --git a/client/start-with/square-payments/style.scss b/client/start-with/square-payments/style.scss
deleted file mode 100644
index bd7b05e9906bf0..00000000000000
--- a/client/start-with/square-payments/style.scss
+++ /dev/null
@@ -1,274 +0,0 @@
-@import "@automattic/components/src/styles/typography";
-@import "@wordpress/base-styles/breakpoints";
-
-.layout.is-section-start-with {
- height: 100%;
- display: grid;
- grid-template-rows: max-content 1fr;
-
- .layout__content {
- height: 100%;
- width: 100%;
- padding: 0;
- background: #fdfdfd;
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
-
-
- .layout__primary,
- .main {
- height: 100%;
- width: 100%;
- padding: 0;
- }
- }
-
- // stylelint-disable declaration-property-unit-allowed-list
- .container {
- height: 100%;
- display: grid;
- place-items: center;
- grid-template-rows: 1fr 128px;
-
- .content {
- max-width: 1280px;
- display: flex;
- flex-direction: row;
-
- .left-column {
- display: flex;
- flex-direction: column;
- gap: 46px;
-
- .title {
- color: var(--studio-gray-80);
- font-family: $font-recoleta;
- font-size: 79px;
- line-height: 105%;
- }
-
- .subtitle {
- color: #000;
- font-family: $font-sf-pro-display;
- font-size: $font-body;
- text-wrap: pretty;
- font-style: normal;
- line-height: 162.5%;
- width: 530px;
- margin-bottom: 0;
- }
-
- .start-store-cta {
- width: fit-content;
- background: #3858e9;
- color: #fff;
- border-radius: 3px;
- padding: 9px 20px;
- font-size: $font-body;
- font-weight: 500;
- line-height: 150%;
- }
- }
-
- .right-column {
- flex-shrink: 0;
-
- img {
- width: 500px;
- }
- }
- }
-
- .footer {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- align-items: center;
-
- .text {
- width: 1358px;
- color: var(--studio-gray-40);
- font-family: $font-sf-pro-display;
- font-size: $font-body;
- font-style: normal;
- line-height: 150%;
- margin-bottom: 18px;
- }
-
- .brands {
- width: 100%;
- background-color: var(--studio-gray-80);
- display: flex;
- align-items: center;
- flex-direction: column;
- justify-content: center;
-
-
- .brands-container {
- padding: 25px 0;
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 50px;
- }
- }
- }
- }
- // stylelint-enable declaration-property-unit-allowed-list
-
- @media (max-width: $break-huge) {
- .container {
- .content {
- max-width: calc(100vw - 100px);
- .left-column {
- .title {
- font-size: $font-headline-large;
- }
- }
-
- .right-column {
- img {
- width: 400px;
- }
- }
- }
-
- .footer {
- .text {
- width: 1182px;
- }
-
- .brands .brands-container {
- img:nth-last-of-type(-n+1) {
- display: none;
- }
- }
- }
- }
- }
-
-
- @media (max-width: $break-wide) {
- .container {
- .footer {
- .text {
- width: 1020px;
- }
-
- .brands .brands-container {
- img:nth-last-of-type(-n+2) {
- display: none;
- }
- }
- }
- }
- }
-
- @media (max-width: $break-xlarge) {
- .container {
- .footer {
- .text {
- width: 884px;
- }
-
- .brands .brands-container {
- img:nth-last-of-type(-n+3) {
- display: none;
- }
- }
- }
- }
- }
-
- @media (max-width: $break-large) {
- .container {
- .content {
- flex-direction: column-reverse;
- align-items: center;
- gap: 100px;
- align-self: start;
-
- .left-column {
- padding-bottom: 20px;
-
- .title {
- width: calc(100vw - 100px);
- font-size: $font-headline-medium;
- }
-
- .subtitle {
- width: calc(100vw - 100px);
- }
- }
-
- .right-column {
- padding-top: 100px;
- }
- }
-
- .footer {
- .text {
- width: 708px;
- }
-
- .brands .brands-container {
- gap: 20px;
- }
- }
- }
- }
-
- @media (max-width: $break-medium) {
- .container {
- .content {
- gap: 5px;
-
- .left-column {
- gap: 16px;
-
- .title {
- font-size: $font-headline-small;
- }
-
- img {
- width: 250px;
- }
- }
-
- .right-column {
- padding-top: 30px;
- }
- }
-
- .footer {
- .text {
- width: 330px;
- }
-
- .brands .brands-container {
- img:nth-last-of-type(-n+6) {
- display: none;
- }
- }
- }
- }
- }
-
- @media (max-height: $break-medium) {
- .container .content {
- display: flex;
- flex-direction: column;
- justify-content: center;
- height: 100%;
-
- .right-column {
- display: none;
- }
- }
- }
-}
-
diff --git a/client/state/a8c-for-agencies/agency/selectors.ts b/client/state/a8c-for-agencies/agency/selectors.ts
index c0f56955191ab3..2156274bcad21d 100644
--- a/client/state/a8c-for-agencies/agency/selectors.ts
+++ b/client/state/a8c-for-agencies/agency/selectors.ts
@@ -1,5 +1,6 @@
// Required for modular state.
import 'calypso/state/a8c-for-agencies/init';
+import { isEnabled } from '@automattic/calypso-config';
import { A4AStore, APIError, Agency } from '../types';
export function getActiveAgency( state: A4AStore ): Agency | null {
@@ -34,3 +35,13 @@ export function hasAgency( state: A4AStore ): boolean {
export function isAgencyClientUser( state: A4AStore ): boolean {
return state.a8cForAgencies.agencies.isAgencyClientUser;
}
+
+export function hasAgencyCapability( state: A4AStore, capability: string ): boolean {
+ if ( ! isEnabled( 'a4a-multi-user-support' ) ) {
+ // This is always true if the feature is not enabled to bypass restrictions.
+ return true;
+ }
+
+ const agency = getActiveAgency( state );
+ return agency?.user?.capabilities?.includes( capability ) ?? false;
+}
diff --git a/client/state/billing-transactions/schema.js b/client/state/billing-transactions/schema.js
index 0125e09bb2b4a2..cf656d6af6d16c 100644
--- a/client/state/billing-transactions/schema.js
+++ b/client/state/billing-transactions/schema.js
@@ -20,6 +20,7 @@ export const billingTransactionsSchema = {
pay_ref: { type: 'string' },
pay_part: { type: 'string' },
cc_type: { type: 'string' },
+ cc_display_type: { type: [ 'string', 'null' ] },
cc_num: { type: 'string' },
cc_name: { type: 'string' },
cc_email: { type: 'string' },
diff --git a/client/state/billing-transactions/types.ts b/client/state/billing-transactions/types.ts
index 7c2c91b9d79198..ec915534ed3018 100644
--- a/client/state/billing-transactions/types.ts
+++ b/client/state/billing-transactions/types.ts
@@ -32,6 +32,7 @@ export interface BillingTransaction {
cc_name: string;
cc_num: string;
cc_type: string;
+ cc_display_brand: string | null;
credit: string;
date: string;
desc: string;
diff --git a/client/state/jetpack-agency-dashboard/actions.ts b/client/state/jetpack-agency-dashboard/actions.ts
index 11d5989087f41e..5f48ec9dbd7475 100644
--- a/client/state/jetpack-agency-dashboard/actions.ts
+++ b/client/state/jetpack-agency-dashboard/actions.ts
@@ -69,7 +69,7 @@ export const updateFilter = ( filter: AgencyDashboardFilterOption[] ) => () => {
updateDashboardURLQueryArgs( { filter } );
};
-export const updateSort = ( sort: DashboardSortInterface ) => () => {
+export const updateSort = ( sort?: DashboardSortInterface ) => () => {
updateDashboardURLQueryArgs( { sort } );
};
diff --git a/config/development.json b/config/development.json
index bc1ad51a0f1e4a..65a832bc612a57 100644
--- a/config/development.json
+++ b/config/development.json
@@ -167,7 +167,7 @@
"page/export": true,
"pattern-assembler/v2": true,
"performance-profiler": true,
- "performance-profiler/llm": false,
+ "performance-profiler/llm": true,
"plans/hosting-trial": true,
"plans/migration-trial": true,
"plans/personal-plan": true,
@@ -196,8 +196,6 @@
"redirect-fallback-browsers": false,
"rum-tracking/logstash": true,
"safari-idb-mitigation": true,
- "start-with/square-payments": true,
- "start-with/stripe": true,
"security/security-checkup": true,
"seller-experience": true,
"server-side-rendering": true,
diff --git a/config/horizon.json b/config/horizon.json
index 49f763c63a8f63..da5cb990be28cb 100644
--- a/config/horizon.json
+++ b/config/horizon.json
@@ -78,7 +78,7 @@
"layout/app-banner": true,
"layout/guided-tours": true,
"layout/query-selected-editor": true,
- "layout/site-level-user-profile": false,
+ "layout/site-level-user-profile": true,
"layout/support-article-dialog": true,
"legal-updates-banner": false,
"livechat_solution": true,
@@ -122,8 +122,6 @@
"readymade-templates/showcase": false,
"redirect-fallback-browsers": true,
"safari-idb-mitigation": true,
- "start-with/square-payments": true,
- "start-with/stripe": false,
"security/security-checkup": true,
"seller-experience": true,
"server-side-rendering": true,
diff --git a/config/production.json b/config/production.json
index 2abb84209dab5d..0aef0ddea73775 100644
--- a/config/production.json
+++ b/config/production.json
@@ -102,7 +102,7 @@
"layout/app-banner": true,
"layout/guided-tours": true,
"layout/query-selected-editor": true,
- "layout/site-level-user-profile": false,
+ "layout/site-level-user-profile": true,
"layout/support-article-dialog": true,
"legal-updates-banner": false,
"livechat_solution": true,
@@ -137,6 +137,7 @@
"p2-enabled": false,
"pattern-assembler/v2": true,
"performance-profiler": false,
+ "performance-profiler/llm": true,
"plans/hosting-trial": true,
"plans/migration-trial": true,
"plans/personal-plan": true,
@@ -164,8 +165,6 @@
"redirect-fallback-browsers": true,
"rum-tracking/logstash": true,
"safari-idb-mitigation": true,
- "start-with/square-payments": true,
- "start-with/stripe": false,
"security/security-checkup": true,
"seller-experience": true,
"server-side-rendering": true,
diff --git a/config/stage.json b/config/stage.json
index 001e6f7737703b..cb63d3da45e4f5 100644
--- a/config/stage.json
+++ b/config/stage.json
@@ -98,7 +98,7 @@
"layout/app-banner": true,
"layout/guided-tours": true,
"layout/query-selected-editor": true,
- "layout/site-level-user-profile": false,
+ "layout/site-level-user-profile": true,
"layout/support-article-dialog": true,
"legal-updates-banner": false,
"livechat_solution": true,
@@ -133,6 +133,7 @@
"page/export": true,
"pattern-assembler/v2": true,
"performance-profiler": false,
+ "performance-profiler/llm": true,
"plans/hosting-trial": true,
"plans/migration-trial": true,
"plans/personal-plan": true,
@@ -160,8 +161,6 @@
"redirect-fallback-browsers": true,
"rum-tracking/logstash": true,
"safari-idb-mitigation": true,
- "start-with/square-payments": true,
- "start-with/stripe": false,
"security/security-checkup": true,
"seller-experience": true,
"server-side-rendering": true,
diff --git a/config/wpcalypso.json b/config/wpcalypso.json
index a56635e4ffa798..c6a97143ffac3c 100644
--- a/config/wpcalypso.json
+++ b/config/wpcalypso.json
@@ -132,6 +132,7 @@
"p2-enabled": false,
"pattern-assembler/v2": true,
"performance-profiler": true,
+ "performance-profiler/llm": true,
"plans/hosting-trial": true,
"plans/migration-trial": true,
"plans/personal-plan": true,
@@ -158,8 +159,7 @@
"redirect-fallback-browsers": true,
"rum-tracking/logstash": true,
"safari-idb-mitigation": true,
- "start-with/square-payments": true,
- "start-with/stripe": true,
+
"security/security-checkup": true,
"seller-experience": true,
"server-side-rendering": true,
diff --git a/package.json b/package.json
index 9515ed0329aa70..162389cf777080 100644
--- a/package.json
+++ b/package.json
@@ -148,7 +148,7 @@
"@automattic/calypso-products": "workspace:^",
"@automattic/calypso-razorpay": "workspace:^",
"@automattic/calypso-router": "workspace:^",
- "@automattic/color-studio": "2.6.0",
+ "@automattic/color-studio": "^3.0.1",
"@automattic/command-palette": "workspace:^",
"@automattic/components": "workspace:^",
"@automattic/data-stores": "workspace:^",
@@ -350,7 +350,7 @@
"@wordpress/customize-widgets": "5.2.0",
"@wordpress/data-controls": "4.2.0",
"@wordpress/data": "^10.2.0",
- "@wordpress/dataviews": "patch:@wordpress/dataviews@npm%3A0.4.1#~/.yarn/patches/@wordpress-dataviews-npm-0.4.1-2c01fa0792.patch",
+ "@wordpress/dataviews": "4.2.0",
"@wordpress/date": "5.2.0",
"@wordpress/dependency-extraction-webpack-plugin": "5.9.0",
"@wordpress/deprecated": "4.2.0",
diff --git a/packages/calypso-color-schemes/package.json b/packages/calypso-color-schemes/package.json
index b861cc14e2570c..d1a5e6a39a6f07 100644
--- a/packages/calypso-color-schemes/package.json
+++ b/packages/calypso-color-schemes/package.json
@@ -28,7 +28,7 @@
"devDependencies": {
"@automattic/calypso-eslint-overrides": "workspace:^",
"@automattic/calypso-typescript-config": "workspace:^",
- "@automattic/color-studio": "2.6.0",
+ "@automattic/color-studio": "^3.0.1",
"postcss": "^8.4.5",
"postcss-custom-properties": "^11.0.0",
"sass": "^1.37.5"
diff --git a/packages/calypso-color-schemes/src/shared/color-schemes/_default.scss b/packages/calypso-color-schemes/src/shared/color-schemes/_default.scss
index 8f3d810e623d99..6e1634d1accafc 100644
--- a/packages/calypso-color-schemes/src/shared/color-schemes/_default.scss
+++ b/packages/calypso-color-schemes/src/shared/color-schemes/_default.scss
@@ -31,36 +31,36 @@
--color-primary-100: var(--studio-blue-100);
--color-primary-100-rgb: var(--studio-blue-100-rgb);
- --color-accent: var(--studio-pink-50);
- --color-accent-rgb: var(--studio-pink-50-rgb);
- --color-accent-dark: var(--studio-pink-70);
- --color-accent-dark-rgb: var(--studio-pink-70-rgb);
- --color-accent-light: var(--studio-pink-30);
- --color-accent-light-rgb: var(--studio-pink-30-rgb);
- --color-accent-0: var(--studio-pink-0);
- --color-accent-0-rgb: var(--studio-pink-0-rgb);
- --color-accent-5: var(--studio-pink-5);
- --color-accent-5-rgb: var(--studio-pink-5-rgb);
- --color-accent-10: var(--studio-pink-10);
- --color-accent-10-rgb: var(--studio-pink-10-rgb);
- --color-accent-20: var(--studio-pink-20);
- --color-accent-20-rgb: var(--studio-pink-20-rgb);
- --color-accent-30: var(--studio-pink-30);
- --color-accent-30-rgb: var(--studio-pink-30-rgb);
- --color-accent-40: var(--studio-pink-40);
- --color-accent-40-rgb: var(--studio-pink-40-rgb);
- --color-accent-50: var(--studio-pink-50);
- --color-accent-50-rgb: var(--studio-pink-50-rgb);
- --color-accent-60: var(--studio-pink-60);
- --color-accent-60-rgb: var(--studio-pink-60-rgb);
- --color-accent-70: var(--studio-pink-70);
- --color-accent-70-rgb: var(--studio-pink-70-rgb);
- --color-accent-80: var(--studio-pink-80);
- --color-accent-80-rgb: var(--studio-pink-80-rgb);
- --color-accent-90: var(--studio-pink-90);
- --color-accent-90-rgb: var(--studio-pink-90-rgb);
- --color-accent-100: var(--studio-pink-100);
- --color-accent-100-rgb: var(--studio-pink-100-rgb);
+ --color-accent: var(--studio-wordpress-blue-50);
+ --color-accent-rgb: var(--studio-wordpress-blue-50-rgb);
+ --color-accent-dark: var(--studio-wordpress-blue-70);
+ --color-accent-dark-rgb: var(--studio-wordpress-blue-70-rgb);
+ --color-accent-light: var(--studio-wordpress-blue-30);
+ --color-accent-light-rgb: var(--studio-wordpress-blue-30-rgb);
+ --color-accent-0: var(--studio-wordpress-blue-0);
+ --color-accent-0-rgb: var(--studio-wordpress-blue-0-rgb);
+ --color-accent-5: var(--studio-wordpress-blue-5);
+ --color-accent-5-rgb: var(--studio-wordpress-blue-5-rgb);
+ --color-accent-10: var(--studio-wordpress-blue-10);
+ --color-accent-10-rgb: var(--studio-wordpress-blue-10-rgb);
+ --color-accent-20: var(--studio-wordpress-blue-20);
+ --color-accent-20-rgb: var(--studio-wordpress-blue-20-rgb);
+ --color-accent-30: var(--studio-wordpress-blue-30);
+ --color-accent-30-rgb: var(--studio-wordpress-blue-30-rgb);
+ --color-accent-40: var(--studio-wordpress-blue-40);
+ --color-accent-40-rgb: var(--studio-wordpress-blue-40-rgb);
+ --color-accent-50: var(--studio-wordpress-blue-50);
+ --color-accent-50-rgb: var(--studio-wordpress-blue-50-rgb);
+ --color-accent-60: var(--studio-wordpress-blue-60);
+ --color-accent-60-rgb: var(--studio-wordpress-blue-60-rgb);
+ --color-accent-70: var(--studio-wordpress-blue-70);
+ --color-accent-70-rgb: var(--studio-wordpress-blue-70-rgb);
+ --color-accent-80: var(--studio-wordpress-blue-80);
+ --color-accent-80-rgb: var(--studio-wordpress-blue-80-rgb);
+ --color-accent-90: var(--studio-wordpress-blue-90);
+ --color-accent-90-rgb: var(--studio-wordpress-blue-90-rgb);
+ --color-accent-100: var(--studio-wordpress-blue-100);
+ --color-accent-100-rgb: var(--studio-wordpress-blue-100-rgb);
--color-neutral: var(--studio-gray-50);
--color-neutral-rgb: var(--studio-gray-50-rgb);
diff --git a/packages/calypso-products/src/products-list.ts b/packages/calypso-products/src/products-list.ts
index 81add7b9f15de2..5f19335cd298ef 100644
--- a/packages/calypso-products/src/products-list.ts
+++ b/packages/calypso-products/src/products-list.ts
@@ -206,7 +206,6 @@ export const JETPACK_SITE_PRODUCTS_WITH_FEATURES: Record<
[ PRODUCT_JETPACK_AI_MONTHLY ]: {
product_name: PRODUCT_SHORT_NAMES[ PRODUCT_JETPACK_AI_MONTHLY ],
product_slug: PRODUCT_JETPACK_AI_MONTHLY,
- product_alias: PRODUCT_JETPACK_AI_MONTHLY_100,
type: PRODUCT_JETPACK_AI_MONTHLY,
term: TERM_MONTHLY,
bill_period: PLAN_MONTHLY_PERIOD,
@@ -278,7 +277,6 @@ export const JETPACK_SITE_PRODUCTS_WITH_FEATURES: Record<
[ PRODUCT_JETPACK_AI_YEARLY ]: {
product_name: PRODUCT_SHORT_NAMES[ PRODUCT_JETPACK_AI_YEARLY ],
product_slug: PRODUCT_JETPACK_AI_YEARLY,
- product_alias: PRODUCT_JETPACK_AI_YEARLY_100,
type: PRODUCT_JETPACK_AI_YEARLY,
term: TERM_ANNUALLY,
bill_period: PLAN_ANNUAL_PERIOD,
@@ -350,7 +348,6 @@ export const JETPACK_SITE_PRODUCTS_WITH_FEATURES: Record<
[ PRODUCT_JETPACK_AI_BI_YEARLY ]: {
product_name: PRODUCT_SHORT_NAMES[ PRODUCT_JETPACK_AI_BI_YEARLY ],
product_slug: PRODUCT_JETPACK_AI_BI_YEARLY,
- product_alias: PRODUCT_JETPACK_AI_BI_YEARLY_100,
type: PRODUCT_JETPACK_AI_BI_YEARLY,
term: TERM_BIENNIALLY,
bill_period: PLAN_BIENNIAL_PERIOD,
diff --git a/packages/calypso-products/src/translations.tsx b/packages/calypso-products/src/translations.tsx
index 2e64a67e631fee..aa30e5eec6724b 100644
--- a/packages/calypso-products/src/translations.tsx
+++ b/packages/calypso-products/src/translations.tsx
@@ -615,6 +615,15 @@ export const getJetpackProductDisclaimers = (
<>>
);
+ const aiAssistantDisclaimer = translate(
+ 'Limits apply for high request capacity. {{link}}Learn more about it here.{{/link}}',
+ {
+ components: {
+ link: getLink(),
+ },
+ }
+ );
+
const monitorDisclaimer = translate( 'Limit of 20 SMS per site, each month.' );
return {
@@ -635,6 +644,9 @@ export const getJetpackProductDisclaimers = (
[ PLAN_JETPACK_COMPLETE_MONTHLY ]: backupDisclaimer,
[ PRODUCT_JETPACK_MONITOR_YEARLY ]: monitorDisclaimer,
[ PRODUCT_JETPACK_MONITOR_MONTHLY ]: monitorDisclaimer,
+ [ PRODUCT_JETPACK_AI_MONTHLY ]: aiAssistantDisclaimer,
+ [ PRODUCT_JETPACK_AI_YEARLY ]: aiAssistantDisclaimer,
+ [ PRODUCT_JETPACK_AI_BI_YEARLY ]: aiAssistantDisclaimer,
};
};
@@ -1545,7 +1557,7 @@ export const getJetpackProductsWhatIsIncluded = (): Record< string, Array< Trans
return {
[ PRODUCT_JETPACK_AI_MONTHLY ]: [
- translate( '100 monthly requests (upgradeable)' ),
+ translate( 'High request capacity *' ),
...aiAssistantIncludesInfo,
],
[ PRODUCT_JETPACK_AI_MONTHLY_100 ]: [
@@ -1569,7 +1581,7 @@ export const getJetpackProductsWhatIsIncluded = (): Record< string, Array< Trans
...aiAssistantIncludesInfo,
],
[ PRODUCT_JETPACK_AI_YEARLY ]: [
- translate( '100 monthly requests (upgradeable)' ),
+ translate( 'High request capacity *' ),
...aiAssistantIncludesInfo,
],
[ PRODUCT_JETPACK_AI_YEARLY_100 ]: [
@@ -1593,7 +1605,7 @@ export const getJetpackProductsWhatIsIncluded = (): Record< string, Array< Trans
...aiAssistantIncludesInfo,
],
[ PRODUCT_JETPACK_AI_BI_YEARLY ]: [
- translate( '100 monthly requests (upgradeable)' ),
+ translate( 'High request capacity *' ),
...aiAssistantIncludesInfo,
],
[ PRODUCT_JETPACK_AI_BI_YEARLY_100 ]: [
diff --git a/packages/components/src/highlight-cards/annual-highlight-cards.tsx b/packages/components/src/highlight-cards/annual-highlight-cards.tsx
index 826b2455803f11..b391450964f4d5 100644
--- a/packages/components/src/highlight-cards/annual-highlight-cards.tsx
+++ b/packages/components/src/highlight-cards/annual-highlight-cards.tsx
@@ -1,8 +1,9 @@
import { comment, Icon, paragraph, people, postContent, starEmpty } from '@wordpress/icons';
import clsx from 'clsx';
-import { useTranslate } from 'i18n-calypso';
+import { translate, useTranslate } from 'i18n-calypso';
import ComponentSwapper from '../component-swapper';
-import CountComparisonCard from './count-comparison-card';
+import CountCard from './count-card';
+import HighlightCardsHeading from './highlight-cards-heading';
import MobileHighlightCardListing from './mobile-highlight-cards';
import './style.scss';
@@ -27,54 +28,33 @@ type AnnualHighlightCardsProps = {
navigation?: React.ReactNode;
};
-function AnnualHighlightsMobile( { counts }: AnnualHighlightsProps ) {
- const translate = useTranslate();
-
- const highlights = [
+function getCardProps( counts: AnnualHighlightCounts ) {
+ return [
{ heading: translate( 'Posts' ), count: counts?.posts, icon: postContent },
{ heading: translate( 'Words' ), count: counts?.words, icon: paragraph },
{ heading: translate( 'Likes' ), count: counts?.likes, icon: starEmpty },
{ heading: translate( 'Comments' ), count: counts?.comments, icon: comment },
{ heading: translate( 'Subscribers' ), count: counts?.followers, icon: people },
];
+}
- return ;
+function AnnualHighlightsMobile( { counts }: AnnualHighlightsProps ) {
+ return ;
}
function AnnualHighlightsStandard( { counts }: AnnualHighlightsProps ) {
- const translate = useTranslate();
+ const props = getCardProps( counts );
return (
- }
- count={ counts?.posts ?? null }
- showValueTooltip
- />
- }
- count={ counts?.words ?? null }
- showValueTooltip
- />
- }
- count={ counts?.likes ?? null }
- showValueTooltip
- />
- }
- count={ counts?.comments ?? null }
- showValueTooltip
- />
- }
- count={ counts?.followers ?? null }
- showValueTooltip
- />
+ { props.map( ( { count, heading, icon }, index ) => (
+ }
+ showValueTooltip
+ />
+ ) ) }
);
}
@@ -89,7 +69,7 @@ export default function AnnualHighlightCards( {
const translate = useTranslate();
const header = (
-
+
{ year != null && Number.isFinite( year )
? translate( '%(year)s in review', { args: { year } } )
: translate( 'Year in review' ) }{ ' ' }
@@ -100,7 +80,7 @@ export default function AnnualHighlightCards( {
) : null }
-
+
);
return (
diff --git a/packages/components/src/highlight-cards/count-card.tsx b/packages/components/src/highlight-cards/count-card.tsx
index cc9209af96a97d..eea7423717d285 100644
--- a/packages/components/src/highlight-cards/count-card.tsx
+++ b/packages/components/src/highlight-cards/count-card.tsx
@@ -1,42 +1,67 @@
-import { Icon } from '@wordpress/icons';
-import { useMemo } from 'react';
-import BaseCard from './base-card';
+import clsx from 'clsx';
+import { useRef, useState } from 'react';
+import { Card } from '../';
+import Popover from '../popover';
+import { formatNumber } from './lib/numbers';
interface CountCardProps {
- heading: React.ReactNode;
- icon: JSX.Element;
- value: number | string;
+ heading?: React.ReactNode;
+ icon?: JSX.Element;
+ note?: string;
+ showValueTooltip?: boolean;
+ value: number | string | null;
}
-function useDisplayValue( value: CountCardProps[ 'value' ] ) {
- return useMemo( () => {
- if ( typeof value === 'string' ) {
- return value;
- }
- if ( typeof value === 'number' ) {
- return value.toLocaleString();
- }
- return '-';
- }, [ value ] );
+function TooltipContent( { value }: CountCardProps ) {
+ return (
+
+
+ { formatNumber( value as number, false ) }
+
+
+ );
}
-export default function CountCard( { heading, icon, value }: CountCardProps ) {
- const displayValue = useDisplayValue( value );
+export default function CountCard( {
+ heading,
+ icon,
+ note,
+ value,
+ showValueTooltip,
+}: CountCardProps ) {
+ const textRef = useRef( null );
+ const [ isTooltipVisible, setTooltipVisible ] = useState( false );
+
+ // Tooltips are used to show the full number instead of the shortened number.
+ // Non-numeric values are not shown in the tooltip.
+ const shouldShowTooltip = showValueTooltip && typeof value === 'number';
return (
-
-
-
-
- { heading }
- >
- }
- >
-
-
{ displayValue }
+
+ { icon && { icon }
}
+ { heading && { heading }
}
+ setTooltipVisible( true ) }
+ onMouseLeave={ () => setTooltipVisible( false ) }
+ >
+
+ { typeof value === 'number' ? formatNumber( value, true ) : value }
+
-
+ { shouldShowTooltip && (
+
+
+ { note && { note }
}
+
+ ) }
+
);
}
diff --git a/packages/components/src/highlight-cards/count-comparison-card.tsx b/packages/components/src/highlight-cards/count-comparison-card.tsx
index d6a6e7ba3b98e6..64867f34349061 100644
--- a/packages/components/src/highlight-cards/count-comparison-card.tsx
+++ b/packages/components/src/highlight-cards/count-comparison-card.tsx
@@ -1,37 +1,20 @@
import { arrowDown, arrowUp, Icon } from '@wordpress/icons';
import clsx from 'clsx';
import { useRef, useState } from 'react';
-import { Card, ShortenedNumber, formattedNumber } from '../';
+import { Card } from '../';
import Popover from '../popover';
+import { formatNumber, formatPercentage, subtract, percentCalculator } from './lib/numbers';
type CountComparisonCardProps = {
count: number | null;
heading?: React.ReactNode;
icon?: React.ReactNode;
onClick?: ( event: MouseEvent ) => void;
- previousCount?: number | null;
+ previousCount: number | null;
showValueTooltip?: boolean | null;
- note?: string;
compact?: boolean;
};
-function subtract( a: number | null, b: number | null | undefined ): number | null {
- return a === null || b === null || b === undefined ? null : a - b;
-}
-
-export function percentCalculator( part: number | null, whole: number | null | undefined ) {
- if ( part === null || whole === null || whole === undefined ) {
- return null;
- }
- // Handle NaN case.
- if ( part === 0 && whole === 0 ) {
- return 0;
- }
- const answer = ( part / whole ) * 100;
- // Handle Infinities.
- return Math.abs( answer ) === Infinity ? 100 : Math.round( answer );
-}
-
type TrendComparisonProps = {
count: number | null;
previousCount?: number | null;
@@ -39,49 +22,63 @@ type TrendComparisonProps = {
export function TrendComparison( { count, previousCount }: TrendComparisonProps ) {
const difference = subtract( count, previousCount );
- const differenceMagnitude = Math.abs( difference as number );
const percentage = Number.isFinite( difference )
? percentCalculator( Math.abs( difference as number ), previousCount )
: null;
- if ( difference === null ) {
+ // Show nothing if inputs are invalid or if there is no change.
+ if ( difference === null || difference === 0 ) {
return null;
}
- return (
+ return Math.abs( difference ) === 0 ? null : (
0,
} ) }
>
-
+
{ difference < 0 && }
{ difference > 0 && }
-
- { differenceMagnitude <= 9999 && formattedNumber( differenceMagnitude ) }
- { differenceMagnitude > 9999 && }
-
{ percentage !== null && (
- ({ percentage }%)
+
+ { ' ' }
+ { formatPercentage( percentage ) }
+
) }
);
}
+function TooltipContent( { count, previousCount }: CountComparisonCardProps ) {
+ const difference = subtract( count, previousCount ) as number;
+ return (
+
+
+ { formatNumber( count, false ) }
+ { ' ' }
+ { difference !== 0 && difference !== null && (
+
+ ({ formatNumber( difference, false, true ) })
+
+ ) }
+
+
+ );
+}
+
export default function CountComparisonCard( {
count,
previousCount,
icon,
heading,
showValueTooltip,
- note = '',
compact = false,
}: CountComparisonCardProps ) {
const textRef = useRef( null );
const [ isTooltipVisible, setTooltipVisible ] = useState( false );
-
return (
{ icon && { icon }
}
@@ -93,12 +90,8 @@ export default function CountComparisonCard( {
onMouseEnter={ () => setTooltipVisible( true ) }
onMouseLeave={ () => setTooltipVisible( false ) }
>
-
-
+
+ { formatNumber( count ) }
{ ' ' }
{ showValueTooltip && (
@@ -108,14 +101,12 @@ export default function CountComparisonCard( {
position="bottom right"
context={ textRef.current }
>
-
-
- { icon && { icon } }
- { heading && { heading } }
-
- { formattedNumber( count ) }
-
- { note && { note }
}
+
) }
diff --git a/packages/components/src/highlight-cards/highlight-cards-heading.tsx b/packages/components/src/highlight-cards/highlight-cards-heading.tsx
new file mode 100644
index 00000000000000..06d8e19480dbdf
--- /dev/null
+++ b/packages/components/src/highlight-cards/highlight-cards-heading.tsx
@@ -0,0 +1,18 @@
+import { useHasEnTranslation } from '@automattic/i18n-utils';
+import { useTranslate } from 'i18n-calypso';
+
+export default function HighlightCardsHeading( { children }: { children: React.ReactNode } ) {
+ const translate = useTranslate();
+ const hasEnTranslation = useHasEnTranslation();
+ const hasTranslation = hasEnTranslation( 'Updates every 30 minutes' );
+ return (
+
+
{ children }
+ { hasTranslation && (
+
+ { translate( 'Updates every 30 minutes' ) }
+
+ ) }
+
+ );
+}
diff --git a/packages/components/src/highlight-cards/lib/numbers.ts b/packages/components/src/highlight-cards/lib/numbers.ts
new file mode 100644
index 00000000000000..45d87b0fc22dee
--- /dev/null
+++ b/packages/components/src/highlight-cards/lib/numbers.ts
@@ -0,0 +1,46 @@
+import importedFormatNumber, {
+ DEFAULT_LOCALE,
+ STANDARD_FORMATTING_OPTIONS,
+ COMPACT_FORMATTING_OPTIONS,
+ PERCENTAGE_FORMATTING_OPTIONS,
+} from '../../number-formatters/lib/format-number';
+
+export function formatNumber( number: number | null, isShortened = true, showSign = false ) {
+ const option = isShortened
+ ? { ...COMPACT_FORMATTING_OPTIONS }
+ : { ...STANDARD_FORMATTING_OPTIONS };
+ if ( showSign ) {
+ option.signDisplay = 'exceptZero';
+ }
+ return importedFormatNumber( number, DEFAULT_LOCALE, option );
+}
+
+export function formatPercentage(
+ number: number | null,
+ usePreciseSmallPercentages: boolean = false
+) {
+ // If the number is < 1%, then use 2 significant digits and maximumFractionDigits of 2.
+ // Otherwise, use the default percentage formatting options.
+ const option =
+ usePreciseSmallPercentages && number && number < 0.01
+ ? { ...PERCENTAGE_FORMATTING_OPTIONS, maximumFractionDigits: 2, maximumSignificantDigits: 2 }
+ : PERCENTAGE_FORMATTING_OPTIONS;
+ return importedFormatNumber( number, DEFAULT_LOCALE, option );
+}
+
+export function subtract( a: number | null, b: number | null | undefined ): number | null {
+ return a === null || b === null || b === undefined ? null : a - b;
+}
+
+export function percentCalculator( part: number | null, whole: number | null | undefined ) {
+ if ( part === null || whole === null || whole === undefined ) {
+ return null;
+ }
+ // Handle NaN case.
+ if ( part === 0 && whole === 0 ) {
+ return 0;
+ }
+ const answer = part / whole;
+ // Handle Infinities as 100%.
+ return ! Number.isFinite( answer ) ? 1 : answer;
+}
diff --git a/packages/components/src/highlight-cards/mobile-highlight-cards.tsx b/packages/components/src/highlight-cards/mobile-highlight-cards.tsx
index 503156171f8270..d2503c6a19cbfc 100644
--- a/packages/components/src/highlight-cards/mobile-highlight-cards.tsx
+++ b/packages/components/src/highlight-cards/mobile-highlight-cards.tsx
@@ -3,6 +3,8 @@ import ShortenedNumber from '../number-formatters';
import { TrendComparison } from './count-comparison-card';
import './style.scss';
+// TODO: Figure out a way to remove this and make count-comparison-card handle the mobile layout.
+
type MobileHighlightCardProps = {
heading: string;
count: number | null;
diff --git a/packages/components/src/highlight-cards/stories/annual-highlight-cards.stories.jsx b/packages/components/src/highlight-cards/stories/annual-highlight-cards.stories.jsx
new file mode 100644
index 00000000000000..9d44bce50a5eef
--- /dev/null
+++ b/packages/components/src/highlight-cards/stories/annual-highlight-cards.stories.jsx
@@ -0,0 +1,42 @@
+import AnnualCards from '../annual-highlight-cards';
+
+export default {
+ title: 'packages/components/Highlight Cards/AnnualHighlightCards',
+ component: AnnualCards,
+ argTypes: {
+ year: { control: 'number' },
+ 'counts.comments': { control: 'number' },
+ 'counts.likes': { control: 'number' },
+ 'counts.posts': { control: 'number' },
+ 'counts.words': { control: 'number' },
+ 'counts.followers': { control: 'number' },
+ },
+};
+
+const Template = ( { year, ...counts } ) => {
+ const countsObject = {
+ comments: counts[ 'counts.comments' ],
+ likes: counts[ 'counts.likes' ],
+ posts: counts[ 'counts.posts' ],
+ words: counts[ 'counts.words' ],
+ followers: counts[ 'counts.followers' ],
+ };
+
+ return (
+
+ );
+};
+
+export const AnnualHighlightCards = Template.bind( {} );
+AnnualHighlightCards.args = {
+ year: 2022,
+ 'counts.comments': 72490,
+ 'counts.likes': 12298,
+ 'counts.posts': 79,
+ 'counts.words': 205035,
+ 'counts.followers': 1113323,
+};
diff --git a/packages/components/src/highlight-cards/stories/base-card.stories.jsx b/packages/components/src/highlight-cards/stories/base-card.stories.jsx
new file mode 100644
index 00000000000000..0e54dd60baa277
--- /dev/null
+++ b/packages/components/src/highlight-cards/stories/base-card.stories.jsx
@@ -0,0 +1,28 @@
+import BaseCard from '../base-card';
+
+export default {
+ title: 'packages/components/Highlight Cards/BaseCard',
+ component: BaseCard,
+ argTypes: {
+ heading: { control: 'text' },
+ body: { control: 'text' },
+ },
+};
+
+const Template = ( { heading, body } ) => {
+ return (
+
+ { heading }
}>
+ { body }
+
+
+ );
+};
+
+export const BaseCard_ = Template.bind( {} );
+BaseCard_.args = {
+ heading: 'Customizable Heading',
+ body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+};
diff --git a/packages/components/src/highlight-cards/stories/card.stories.jsx b/packages/components/src/highlight-cards/stories/card.stories.jsx
deleted file mode 100644
index 54c24cf265156f..00000000000000
--- a/packages/components/src/highlight-cards/stories/card.stories.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { institution } from '@wordpress/icons';
-import BaseCard from '../base-card';
-import CountCard from '../count-card';
-
-export default { title: 'packages/components/Highlight Card' };
-
-export const BaseCard_ = () => (
- <>
-
With Heading and Body }>
-
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
- labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
- laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
- voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
- cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
-
-
-
- This card only has a body. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
- eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
- nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
- irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
- Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
- anim id est laborum.
-
-
- With Only a Heading }>
- >
-);
-
-export const CountCard_ = () => (
- <>
-
-
- >
-);
diff --git a/packages/components/src/highlight-cards/stories/cards.stories.jsx b/packages/components/src/highlight-cards/stories/cards.stories.jsx
deleted file mode 100644
index 84c8a486678d31..00000000000000
--- a/packages/components/src/highlight-cards/stories/cards.stories.jsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { action } from '@storybook/addon-actions';
-import AnnualCards from '../annual-highlight-cards';
-import WeeklyCards from '../weekly-highlight-cards';
-
-export default { title: 'packages/components/Highlight Cards' };
-
-const handleClick = action( 'click' );
-
-const WeeklyVariations = ( props ) => (
-
-);
-
-export const WeeklyHighlights = () => ;
-
-export const WeeklyHighlightsWithPartialPreviousCounts = () => (
-
-);
-export const WeeklyHighlightsWithoutPreviousCounts = () => (
-
-);
-
-export const WeeklyHighlightsWithoutCounts = () => (
-
-);
-
-export const AnnualHighlights = () => (
-
-);
diff --git a/packages/components/src/highlight-cards/stories/count-card.stories.jsx b/packages/components/src/highlight-cards/stories/count-card.stories.jsx
new file mode 100644
index 00000000000000..cb5db9f460963e
--- /dev/null
+++ b/packages/components/src/highlight-cards/stories/count-card.stories.jsx
@@ -0,0 +1,36 @@
+import { Icon, institution } from '@wordpress/icons';
+import CountCard from '../count-card';
+
+export default {
+ title: 'packages/components/Highlight Cards/CountCard',
+ component: CountCard,
+ argTypes: {
+ heading: { control: 'text' },
+ valueType: {
+ control: { type: 'radio' },
+ options: [ 'number', 'string' ],
+ },
+ stringValue: { control: 'text' },
+ numberValue: { control: 'number' },
+ },
+};
+
+const Template = ( args ) => {
+ const value = args.valueType === 'string' ? args.stringValue : args.numberValue;
+
+ return (
+
+ } value={ value } />
+
+ );
+};
+
+export const CountCard_ = Template.bind( {} );
+CountCard_.args = {
+ heading: 'Customizable Heading',
+ valueType: 'number',
+ stringValue: 'A really long string message that forces the box to expand',
+ numberValue: 12345,
+};
diff --git a/packages/components/src/highlight-cards/stories/count-comparison-card.stories.jsx b/packages/components/src/highlight-cards/stories/count-comparison-card.stories.jsx
new file mode 100644
index 00000000000000..708abe3aaf3f71
--- /dev/null
+++ b/packages/components/src/highlight-cards/stories/count-comparison-card.stories.jsx
@@ -0,0 +1,36 @@
+import { Icon, institution } from '@wordpress/icons';
+import CountComparisonCard from '../count-comparison-card';
+
+export default {
+ title: 'packages/components/Highlight Cards/CountComparisonCard',
+ component: CountComparisonCard,
+ argTypes: {
+ heading: { control: 'text' },
+ previousCount: { control: 'number' },
+ count: { control: 'number' },
+ showValueTooltip: { control: 'boolean' },
+ },
+};
+
+const Template = ( { count, previousCount, heading } ) => {
+ return (
+
+ }
+ count={ count }
+ previousCount={ previousCount }
+ />
+
+ );
+};
+
+export const CountComparisonCard_ = Template.bind( {} );
+CountComparisonCard_.args = {
+ heading: 'Customizable Heading',
+ count: 234567,
+ previousCount: 123456,
+ showValueTooltip: false,
+};
diff --git a/packages/components/src/highlight-cards/stories/weekly-highlight.stories.jsx b/packages/components/src/highlight-cards/stories/weekly-highlight.stories.jsx
new file mode 100644
index 00000000000000..8dbf2785ef4808
--- /dev/null
+++ b/packages/components/src/highlight-cards/stories/weekly-highlight.stories.jsx
@@ -0,0 +1,71 @@
+import { action } from '@storybook/addon-actions';
+import WeeklyCards from '../weekly-highlight-cards';
+
+export default {
+ title: 'packages/components/Highlight Cards/WeeklyHighlightCards',
+ component: WeeklyCards,
+ argTypes: {
+ 'counts.comments': { control: 'number' },
+ 'counts.likes': { control: 'number' },
+ 'counts.views': { control: 'number' },
+ 'counts.visitors': { control: 'number' },
+ 'previousCounts.comments': { control: 'number' },
+ 'previousCounts.likes': { control: 'number' },
+ 'previousCounts.views': { control: 'number' },
+ 'previousCounts.visitors': { control: 'number' },
+ 'story.isLoading': { control: 'boolean' },
+ showValueTooltip: { control: 'boolean' },
+ },
+};
+
+const handleClick = action( 'click' );
+
+const Template = ( { showValueTooltip, ...args } ) => {
+ const counts = args[ 'story.isLoading' ]
+ ? {}
+ : {
+ comments: args[ 'counts.comments' ],
+ likes: args[ 'counts.likes' ],
+ views: args[ 'counts.views' ],
+ visitors: args[ 'counts.visitors' ],
+ };
+
+ const previousCounts = args[ 'story.isLoading' ]
+ ? {}
+ : {
+ comments: args[ 'previousCounts.comments' ],
+ likes: args[ 'previousCounts.likes' ],
+ views: args[ 'previousCounts.views' ],
+ visitors: args[ 'previousCounts.visitors' ],
+ };
+
+ return (
+
+
+
+ );
+};
+
+export const WeeklyHighlightCards = Template.bind( {} );
+WeeklyHighlightCards.args = {
+ 'counts.comments': 45,
+ 'counts.likes': 0,
+ 'counts.views': 4673,
+ 'counts.visitors': 1548,
+ 'previousCounts.comments': 45,
+ 'previousCounts.likes': 100,
+ 'previousCounts.views': 4073,
+ 'previousCounts.visitors': 1412,
+ showValueTooltip: true,
+ 'story.isLoading': false,
+};
diff --git a/packages/components/src/highlight-cards/style.scss b/packages/components/src/highlight-cards/style.scss
index b973d234480016..4a62347c78c7a5 100644
--- a/packages/components/src/highlight-cards/style.scss
+++ b/packages/components/src/highlight-cards/style.scss
@@ -133,10 +133,13 @@ $highlight-card-tooltip-font: Inter, $sans !default;
.highlight-card {
border-color: var(--studio-gray-5);
border-radius: 5px; // stylelint-disable-line scales/radii
- width: 100%;
- min-width: 180px; // Minimum mobile width
padding: 16px 24px;
margin-bottom: 0;
+ flex-grow: 1;
+ flex-shrink: 0;
+
+ // Ensure minimum of ~1:1 aspect ratio.
+ min-width: 120px;
margin-right: 24px;
&:last-child {
@@ -145,10 +148,6 @@ $highlight-card-tooltip-font: Inter, $sans !default;
}
}
-.highlight-card-icon {
- margin-bottom: 24px;
-}
-
.highlight-card-heading {
font-weight: 500;
line-height: 20px;
@@ -159,8 +158,7 @@ $highlight-card-tooltip-font: Inter, $sans !default;
.highlight-card-count {
align-items: flex-end;
display: flex;
- flex-wrap: wrap;
- font-size: 36px; // stylelint-disable-line declaration-property-unit-allowed-list
+ font-size: 2.25rem;
font-weight: 400;
line-height: 40px;
@@ -223,7 +221,6 @@ $highlight-card-tooltip-font: Inter, $sans !default;
&.highlight-card-popover { // overload tooltip's styles
.popover__inner {
padding: 16px 24px;
- width: 244px;
box-sizing: border-box;
border-radius: 5px; // stylelint-disable-line scales/radii
}
@@ -385,6 +382,7 @@ $highlight-card-tooltip-font: Inter, $sans !default;
.highlight-card-tooltip-label {
display: flex;
align-items: center;
+ margin-right: 1.5em;
}
.highlight-card-tooltip-icon {
fill: var(--studio-white);
@@ -393,6 +391,10 @@ $highlight-card-tooltip-font: Inter, $sans !default;
}
}
+.highlight-card-tooltip-count-difference {
+ color: var(--studio-gray-10);
+}
+
.highlight-card-tooltip-note {
font-size: $font-body-extra-small;
padding-top: 8px;
@@ -431,6 +433,11 @@ $highlight-card-tooltip-font: Inter, $sans !default;
padding-right: $font-body-small;
}
+ .highlight-cards-heading__update-frequency {
+ padding-left: $font-body-small;
+ padding-right: $font-body-small;
+ }
+
// Show count and difference on newlines.
.highlight-card-count {
align-items: flex-start;
@@ -490,3 +497,21 @@ $highlight-card-tooltip-font: Inter, $sans !default;
padding-left: 8px;
}
}
+
+.highlight-cards-heading__wrapper {
+ margin-bottom: $header-margin-bottom;
+
+ .highlight-cards-heading {
+ margin-bottom: 0;
+ }
+
+ .highlight-cards-heading__update-frequency {
+ line-height: 16px;
+
+ span {
+ color: var(--color-text-subtle);
+ display: inline-block;
+ font-size: $font-body-small;
+ }
+ }
+}
diff --git a/packages/components/src/highlight-cards/weekly-highlight-cards.tsx b/packages/components/src/highlight-cards/weekly-highlight-cards.tsx
index efde912f8334e3..7ea9708335e141 100644
--- a/packages/components/src/highlight-cards/weekly-highlight-cards.tsx
+++ b/packages/components/src/highlight-cards/weekly-highlight-cards.tsx
@@ -15,6 +15,7 @@ import { eye } from '../icons';
import Popover from '../popover';
import { comparingInfoBarsChart, comparingInfoRangeChart } from './charts';
import CountComparisonCard from './count-comparison-card';
+import HighlightCardsHeading from './highlight-cards-heading';
import MobileHighlightCardListing from './mobile-highlight-cards';
import './style.scss';
@@ -253,7 +254,7 @@ export default function WeeklyHighlightCards( {
return (
-
+
{ currentPeriod === PAST_THIRTY_DAYS
? translate( '30-day highlights' )
@@ -321,7 +322,7 @@ export default function WeeklyHighlightCards( {
showTooltip={ showSettingsTooltip }
/>
) }
-
+
- { formatNumber( value ) }
-
- );
+ return { formatNumber( value ) } ;
}
diff --git a/packages/components/src/number-formatters/lib/format-number.ts b/packages/components/src/number-formatters/lib/format-number.ts
index 1d4de89e17c753..3ff7bb72ae0f90 100644
--- a/packages/components/src/number-formatters/lib/format-number.ts
+++ b/packages/components/src/number-formatters/lib/format-number.ts
@@ -4,11 +4,25 @@ const warnOnce = memoize( console.warn ); // eslint-disable-line no-console
export const DEFAULT_LOCALE =
( typeof window === 'undefined' ? null : window.navigator?.language ) ?? 'en-US';
-export const DEFAULT_OPTIONS = {
- compactDisplay: 'short',
- maximumFractionDigits: 1,
+
+// Preset Options
+export const COMPACT_FORMATTING_OPTIONS = {
notation: 'compact',
+ maximumSignificantDigits: 3,
+ maximumFractionDigits: 1,
+ compactDisplay: 'short',
+} as Intl.NumberFormatOptions;
+export const STANDARD_FORMATTING_OPTIONS = {
+ notation: 'standard',
} as Intl.NumberFormatOptions;
+export const PERCENTAGE_FORMATTING_OPTIONS = {
+ style: 'percent',
+} as Intl.NumberFormatOptions;
+
+// Default Options
+export const DEFAULT_OPTIONS = { ...COMPACT_FORMATTING_OPTIONS };
+// For backward compatibility; original implementation did not specify max sigfigs.
+delete DEFAULT_OPTIONS.maximumSignificantDigits;
export default function formatNumber(
number: number | null,
@@ -24,7 +38,7 @@ export default function formatNumber(
// This approach ensures a smooth user experience by avoiding disruption for unaffected users.
// Refer to https://github.com/Automattic/wp-calypso/issues/77635 for more details.
try {
- new Intl.NumberFormat( locale, options ).format( number as number );
+ return new Intl.NumberFormat( locale, options ).format( number );
} catch ( error: unknown ) {
warnOnce(
`formatted-number numberFormat error: Intl.NumberFormat().format( ${ typeof number } )`,
@@ -43,7 +57,7 @@ export default function formatNumber(
const optionNamesToRemove = [ 'signDisplay', 'compactDisplay' ];
// Create new format options object with problematic parameters removed.
- const reducedFormatOptions: Record< string, string > = {};
+ const reducedFormatOptions: Record< string, boolean | number | string > = {};
for ( const [ key, value ] of Object.entries( options ) ) {
if ( optionsToRemove[ key ] && value === optionsToRemove[ key ] ) {
continue;
diff --git a/packages/composite-checkout/package.json b/packages/composite-checkout/package.json
index cdfee00c1b4002..4da4a0626d4ea7 100644
--- a/packages/composite-checkout/package.json
+++ b/packages/composite-checkout/package.json
@@ -46,7 +46,7 @@
"devDependencies": {
"@automattic/calypso-storybook": "workspace:^",
"@automattic/calypso-typescript-config": "workspace:^",
- "@automattic/color-studio": "2.6.0",
+ "@automattic/color-studio": "^3.0.1",
"@storybook/cli": "^7.6.19",
"@storybook/react": "^7.6.19",
"@testing-library/dom": "^10.1.0",
diff --git a/packages/composite-checkout/src/lib/swatches.ts b/packages/composite-checkout/src/lib/swatches.ts
index 691333e7151fc7..7fca61a9a26a0f 100644
--- a/packages/composite-checkout/src/lib/swatches.ts
+++ b/packages/composite-checkout/src/lib/swatches.ts
@@ -1,15 +1,4 @@
export type Swatches = {
- wordpressBlue5: string;
- wordpressBlue40: string;
- wordpressBlue50: string;
- wordpressBlue60: string;
- wordpressBlue80: string;
- blue5: string;
- blue30: string;
- blue40: string;
- blue50: string;
- blue60: string;
- blue80: string;
gray0: string;
gray5: string;
gray10: string;
@@ -28,17 +17,6 @@ export type Swatches = {
};
export const swatches: Swatches = {
- wordpressBlue5: '#BEDAE6',
- wordpressBlue40: '#187AA2',
- wordpressBlue50: '#006088',
- wordpressBlue60: '#004E6E',
- wordpressBlue80: '#002C40',
- blue5: '#BBE0FA',
- blue30: '#399CE3',
- blue40: '#1689DB',
- blue50: '#0675C4',
- blue60: '#055D9C',
- blue80: '#02395C',
gray0: '#F6F7F7',
gray5: '#DCDCDE',
gray10: '#C3C4C7',
diff --git a/packages/composite-checkout/src/lib/theme.ts b/packages/composite-checkout/src/lib/theme.ts
index 56d6f54abae27f..0a0d5b9913261e 100644
--- a/packages/composite-checkout/src/lib/theme.ts
+++ b/packages/composite-checkout/src/lib/theme.ts
@@ -60,12 +60,12 @@ const theme: Theme = {
colors: {
background: swatches.gray0,
surface: swatches.white,
- primary: colorStudio.colors[ 'Blue 50' ],
+ primary: colorStudio.colors[ 'WordPress Blue 50' ],
primaryBorder: swatches.pink80,
- primaryOver: colorStudio.colors[ 'Blue 60' ],
- highlight: swatches.blue50,
- highlightBorder: swatches.blue80,
- highlightOver: swatches.blue60,
+ primaryOver: colorStudio.colors[ 'WordPress Blue 60' ],
+ highlight: colorStudio.colors[ 'WordPress Blue 50' ],
+ highlightBorder: colorStudio.colors[ 'WordPress Blue 80' ],
+ highlightOver: colorStudio.colors[ 'WordPress Blue 60' ],
success: colorStudio.colors[ 'Green 30' ],
discount: colorStudio.colors[ 'Green 30' ],
disabledPaymentButtons: colorStudio.colors[ 'Gray 5' ],
@@ -81,7 +81,7 @@ const theme: Theme = {
textColorDisabled: colorStudio.colors[ 'Gray 10' ],
error: swatches.red50,
warningBackground: swatches.red0,
- outline: swatches.blue30,
+ outline: colorStudio.colors[ 'WordPress Blue 30' ],
applePayButtonColor: swatches.black,
applePayButtonRollOverColor: swatches.gray80,
noticeBackground: swatches.gray80,
diff --git a/packages/data-stores/src/onboard/actions.ts b/packages/data-stores/src/onboard/actions.ts
index 3f77e7e2faa7da..1076baeec78e2a 100644
--- a/packages/data-stores/src/onboard/actions.ts
+++ b/packages/data-stores/src/onboard/actions.ts
@@ -412,6 +412,13 @@ export const setDomainCartItem = ( domainCartItem: MinimalRequestCartProduct | u
domainCartItem,
} );
+export const setDomainCartItems = (
+ domainCartItems: MinimalRequestCartProduct[] | undefined
+) => ( {
+ type: 'SET_DOMAIN_CART_ITEMS' as const,
+ domainCartItems,
+} );
+
export const setDomainsTransferData = ( bulkDomainsData: DomainTransferData | undefined ) => ( {
type: 'SET_DOMAINS_TRANSFER_DATA' as const,
bulkDomainsData,
@@ -501,6 +508,7 @@ export type OnboardAction = ReturnType<
| typeof resetSelectedDesign
| typeof setDomainForm
| typeof setDomainCartItem
+ | typeof setDomainCartItems
| typeof setSiteDescription
| typeof setSiteLogo
| typeof setSiteAccentColor
diff --git a/packages/data-stores/src/onboard/reducer.ts b/packages/data-stores/src/onboard/reducer.ts
index f7723270f38020..ba69990aeb87b7 100644
--- a/packages/data-stores/src/onboard/reducer.ts
+++ b/packages/data-stores/src/onboard/reducer.ts
@@ -455,6 +455,20 @@ const domainCartItem: Reducer< MinimalRequestCartProduct | undefined, OnboardAct
return state;
};
+const domainCartItems: Reducer< MinimalRequestCartProduct[] | undefined, OnboardAction > = (
+ state = undefined,
+ action
+) => {
+ if ( action.type === 'SET_DOMAIN_CART_ITEMS' ) {
+ return action.domainCartItems;
+ }
+ if ( action.type === 'RESET_ONBOARD_STORE' ) {
+ return undefined;
+ }
+
+ return state;
+};
+
const isMigrateFromWp: Reducer< boolean, OnboardAction > = ( state = false, action ) => {
if ( action.type === 'SET_IS_MIGRATE_FROM_WP' ) {
return action.isMigrateFromWp;
@@ -617,6 +631,7 @@ const reducer = combineReducers( {
planCartItem,
productCartItems,
isMigrateFromWp,
+ domainCartItems,
pluginsToVerify,
profilerData,
paidSubscribers,
diff --git a/packages/data-stores/src/onboard/selectors.ts b/packages/data-stores/src/onboard/selectors.ts
index 77e93abe2042e9..897e4d6cf9c416 100644
--- a/packages/data-stores/src/onboard/selectors.ts
+++ b/packages/data-stores/src/onboard/selectors.ts
@@ -72,6 +72,7 @@ export const hasSelectedDesign = ( state: State ) => !! state.selectedDesign;
export const getDomainForm = ( state: State ) => state.domainForm;
export const getDomainCartItem = ( state: State ) => state.domainCartItem;
+export const getDomainCartItems = ( state: State ) => state.domainCartItems;
export const getHideFreePlan = ( state: State ) => state.hideFreePlan;
export const getHidePlansFeatureComparison = ( state: State ) => state.hidePlansFeatureComparison;
export const getIsMigrateFromWp = ( state: State ) => state.isMigrateFromWp;
diff --git a/packages/data-stores/src/purchases/lib/assembler.ts b/packages/data-stores/src/purchases/lib/assembler.ts
index b392c65a29d5f9..49157f9ed00ad6 100644
--- a/packages/data-stores/src/purchases/lib/assembler.ts
+++ b/packages/data-stores/src/purchases/lib/assembler.ts
@@ -126,6 +126,7 @@ export function createPurchaseObject( purchase: RawPurchase | RawPurchaseCreditC
object.payment.creditCard = {
id: Number( purchase.payment_card_id ),
type: purchase.payment_card_type,
+ displayBrand: purchase.payment_card_display_brand,
processor: purchase.payment_card_processor,
number: purchase.payment_details,
expiryDate: purchase.payment_expiry,
diff --git a/packages/data-stores/src/purchases/types.ts b/packages/data-stores/src/purchases/types.ts
index 956a511210f577..b6e852fe7c9031 100644
--- a/packages/data-stores/src/purchases/types.ts
+++ b/packages/data-stores/src/purchases/types.ts
@@ -224,6 +224,7 @@ export interface RawPurchase {
| 'emergent-paywall'
| 'brazil-tef'
| string;
+ payment_card_display_brand: string | null;
payment_country_name: string;
payment_country_code: string | null;
stored_details_id: string | null;
@@ -269,6 +270,7 @@ export interface RawPurchase {
export type RawPurchaseCreditCard = RawPurchase & {
payment_type: 'credit_card';
payment_card_type: string;
+ payment_card_display_brand: string | null;
payment_card_processor: string;
payment_details: string | number;
payment_expiry: string;
@@ -337,12 +339,14 @@ export type PurchasePaymentWithCreditCard = PurchasePayment & {
countryName: string | undefined;
storedDetailsId: string | number;
type: string;
+ displayBrand: string | null;
creditCard: PurchasePaymentCreditCard;
};
export interface PurchasePaymentCreditCard {
id: number;
type: string;
+ displayBrand: string | null;
processor: string;
number: string;
expiryDate: string;
diff --git a/packages/data-stores/src/subscriber/actions.ts b/packages/data-stores/src/subscriber/actions.ts
index b38dfcc28ced37..d5c9889e314a54 100644
--- a/packages/data-stores/src/subscriber/actions.ts
+++ b/packages/data-stores/src/subscriber/actions.ts
@@ -39,7 +39,12 @@ export function createActions() {
job,
} );
- function* importCsvSubscribers( siteId: number, file?: File, emails: string[] = [] ) {
+ function* importCsvSubscribers(
+ siteId: number,
+ file?: File,
+ emails: string[] = [],
+ parseOnly: boolean = false
+ ) {
yield importCsvSubscribersStart( siteId, file, emails );
try {
@@ -52,7 +57,7 @@ export function createActions() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
formData: file && [ [ 'import', file, file.name ] ],
- body: { emails },
+ body: { emails, parse_only: parseOnly },
} );
yield importCsvSubscribersStartSuccess( siteId, data.upload_id );
diff --git a/packages/design-picker/src/components/assets/images/assembler-illustration-v2.png b/packages/design-picker/src/components/assets/images/assembler-illustration-v2.png
new file mode 100644
index 00000000000000..07a45387827fad
Binary files /dev/null and b/packages/design-picker/src/components/assets/images/assembler-illustration-v2.png differ
diff --git a/packages/design-picker/src/components/pattern-assembler-cta/index.tsx b/packages/design-picker/src/components/pattern-assembler-cta/index.tsx
index 7705d4443fd38b..6c27a900e04340 100644
--- a/packages/design-picker/src/components/pattern-assembler-cta/index.tsx
+++ b/packages/design-picker/src/components/pattern-assembler-cta/index.tsx
@@ -2,7 +2,7 @@ import { Button } from '@automattic/components';
import { useViewportMatch } from '@wordpress/compose';
import { useTranslate } from 'i18n-calypso';
import { ReactNode } from 'react';
-import assemblerIllustrationImage from '../assets/images/assembler-illustration.png';
+import assemblerIllustrationV2Image from '../assets/images/assembler-illustration-v2.png';
import './style.scss';
type PatternAssemblerCtaData = {
@@ -49,7 +49,7 @@ const PatternAssemblerCta = ( { onButtonClick }: PatternAssemblerCtaProps ) => {
diff --git a/packages/design-picker/src/components/pattern-assembler-cta/style.scss b/packages/design-picker/src/components/pattern-assembler-cta/style.scss
index 2a79ca5aeb1bc4..c4b3fb695041a1 100644
--- a/packages/design-picker/src/components/pattern-assembler-cta/style.scss
+++ b/packages/design-picker/src/components/pattern-assembler-cta/style.scss
@@ -132,7 +132,6 @@
}
.pattern-assembler-cta__image-wrapper {
- padding-left: 35px;
text-align: right;
}
diff --git a/packages/design-picker/src/components/style.scss b/packages/design-picker/src/components/style.scss
index b2d43f32ce0b70..e8c3d3c3368e29 100644
--- a/packages/design-picker/src/components/style.scss
+++ b/packages/design-picker/src/components/style.scss
@@ -310,20 +310,18 @@
}
.theme-card {
- &:hover,
- &:focus-within {
- .theme-card__image-container {
- border-color: #a7aaad;
+ .theme-card--is-active {
+ &:hover,
+ &:focus-within {
+ .theme-card__image-container {
+ border-color: #a7aaad;
+ }
}
}
-
- .theme-card__image-container {
- border-color: rgba(0, 0, 0, 0.12);
- }
}
.design-button-container .design-picker__design-option .design-picker__image-frame:hover::after,
- .theme-card .theme-card__image:hover::after {
+ .theme-card--is-actionable .theme-card__image:hover::after {
background-color: rgba(255, 255, 255, 0.72);
}
}
diff --git a/packages/design-picker/src/components/theme-card/index.tsx b/packages/design-picker/src/components/theme-card/index.tsx
index 619b5e7c903516..ad829efea43559 100644
--- a/packages/design-picker/src/components/theme-card/index.tsx
+++ b/packages/design-picker/src/components/theme-card/index.tsx
@@ -29,6 +29,28 @@ interface ThemeCardProps {
onStyleVariationMoreClick?: () => void;
}
+const ActiveBadge = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ { translate( 'Active', {
+ context: 'singular noun, the currently active theme',
+ } ) }
+
+
+
+ );
+};
+
const ThemeCard = forwardRef(
(
{
@@ -123,14 +145,7 @@ const ThemeCard = forwardRef(
{ name }
- { isActive && (
-
- { translate( 'Active', {
- context: 'singular noun, the currently active theme',
- } ) }
-
- ) }
- { ! isActive && styleVariations.length > 0 && (
+ { ! optionsMenu && styleVariations.length > 0 && (
{ badge }> }
{ optionsMenu && { optionsMenu }
}
+ { isActive && }
diff --git a/packages/design-picker/src/components/theme-card/style.scss b/packages/design-picker/src/components/theme-card/style.scss
index 2d6277a9260a8a..a6495e24ec2139 100644
--- a/packages/design-picker/src/components/theme-card/style.scss
+++ b/packages/design-picker/src/components/theme-card/style.scss
@@ -14,28 +14,8 @@ $theme-card-info-margin-top: 16px;
&--is-active {
.theme-card__image-container {
- border: 0;
- }
-
- .theme-card__info {
- align-items: center;
- background: var(--color-primary);
- border-bottom-left-radius: 2px;
- border-bottom-right-radius: 2px;
- box-sizing: initial;
- flex-direction: row;
- gap: 16px;
- margin-top: 0;
- padding: 8px 24px;
-
- .theme-card__info-options,
- .theme-card__info-options .theme__more-button {
- position: relative;
- }
- }
-
- .theme-card__info-title {
- color: var(--color-text-inverted);
+ box-shadow: 0 0 0 2px var(--color-primary);
+ border-radius: 4px;
}
}
@@ -44,6 +24,23 @@ $theme-card-info-margin-top: 16px;
cursor: pointer;
}
}
+
+ .theme-card__info-badge-container {
+ display: flex;
+ flex-basis: 100%;
+ }
+
+ .theme-card__info-badge-active {
+ display: flex;
+ background-color: var(--color-primary);
+ color: var(--studio-white);
+ font-weight: 400;
+
+ svg {
+ margin-right: 3px;
+ margin-top: 1px;
+ }
+ }
}
.theme-card__content {
@@ -60,6 +57,24 @@ $theme-card-info-margin-top: 16px;
position: relative;
}
+.theme-card--is-actionable {
+ .theme-card__image {
+ &:hover,
+ &:focus {
+ opacity: 0.9;
+
+ .theme-card__image-label {
+ opacity: 1;
+ animation: theme-card__image-label 150ms ease-in-out;
+ }
+ }
+
+ .accessible-focus &:focus &-label {
+ box-shadow: 0 0 0 2px var(--color-primary-light);
+ }
+ }
+}
+
.theme-card__image {
cursor: default;
height: 100%;
@@ -70,20 +85,6 @@ $theme-card-info-margin-top: 16px;
transition: all 200ms ease-in-out;
width: 100%;
- &:hover,
- &:focus {
- opacity: 0.9;
-
- .theme-card__image-label {
- opacity: 1;
- animation: theme-card__image-label 150ms ease-in-out;
- }
- }
-
- .accessible-focus &:focus &-label {
- box-shadow: 0 0 0 2px var(--color-primary-light);
- }
-
&-label {
background: var(--color-surface);
border: 1px solid var(--color-neutral-0);
@@ -293,7 +294,6 @@ $theme-card-info-margin-top: 16px;
.theme-card__info-options,
.theme-card__info-options .theme__more-button {
border: 0;
- bottom: 0;
display: flex;
flex: 0 0 auto;
height: 20px;
diff --git a/packages/design-picker/src/components/unified-design-picker.tsx b/packages/design-picker/src/components/unified-design-picker.tsx
index 466c57cb0cdc00..610f86b57e5ef4 100644
--- a/packages/design-picker/src/components/unified-design-picker.tsx
+++ b/packages/design-picker/src/components/unified-design-picker.tsx
@@ -178,6 +178,7 @@ interface DesignCardProps {
onPreview: ( design: Design, variation?: StyleVariation ) => void;
getBadge: ( themeId: string, isLockedStyleVariation: boolean ) => React.ReactNode;
oldHighResImageLoading?: boolean; // Temporary for A/B test.
+ isActive: boolean;
}
const DesignCard: React.FC< DesignCardProps > = ( {
@@ -190,6 +191,7 @@ const DesignCard: React.FC< DesignCardProps > = ( {
onPreview,
getBadge,
oldHighResImageLoading,
+ isActive,
} ) => {
const [ selectedStyleVariation, setSelectedStyleVariation ] = useState< StyleVariation >();
@@ -203,6 +205,11 @@ const DesignCard: React.FC< DesignCardProps > = ( {
shouldLimitGlobalStyles,
} );
+ const conditionalProps =
+ ! isLocked && isActive
+ ? {}
+ : { onImageClick: () => onPreview( design, selectedStyleVariation ) };
+
return (
= ( {
badge={ getBadge( design.slug, isLocked ) }
styleVariations={ style_variations }
selectedStyleVariation={ selectedStyleVariation }
- onImageClick={ () => onPreview( design, selectedStyleVariation ) }
onStyleVariationClick={ ( variation ) => {
onChangeVariation( design, variation );
setSelectedStyleVariation( variation );
} }
onStyleVariationMoreClick={ () => onPreview( design ) }
+ isActive={ isActive && ! isLocked }
+ { ...conditionalProps }
/>
);
};
@@ -244,6 +252,8 @@ interface DesignPickerProps {
getBadge: ( themeId: string, isLockedStyleVariation: boolean ) => React.ReactNode;
oldHighResImageLoading?: boolean; // Temporary for A/B test
isSiteAssemblerEnabled?: boolean; // Temporary for A/B test
+ siteActiveTheme?: string | null;
+ showActiveThemeBadge?: boolean;
}
const DesignPicker: React.FC< DesignPickerProps > = ( {
@@ -259,6 +269,8 @@ const DesignPicker: React.FC< DesignPickerProps > = ( {
getBadge,
oldHighResImageLoading,
isSiteAssemblerEnabled,
+ siteActiveTheme = null,
+ showActiveThemeBadge = false,
} ) => {
const hasCategories = !! Object.keys( categorization?.categories || {} ).length;
const filteredDesigns = useMemo( () => {
@@ -269,6 +281,8 @@ const DesignPicker: React.FC< DesignPickerProps > = ( {
return designs;
}, [ designs, categorization?.selection ] );
+ // Pick design
+
const assemblerCtaData = usePatternAssemblerCtaData();
return (
@@ -312,6 +326,7 @@ const DesignPicker: React.FC< DesignPickerProps > = ( {
onPreview={ onPreview }
getBadge={ getBadge }
oldHighResImageLoading={ oldHighResImageLoading }
+ isActive={ showActiveThemeBadge && design.recipe?.stylesheet === siteActiveTheme }
/>
);
} ) }
@@ -338,6 +353,8 @@ export interface UnifiedDesignPickerProps {
getBadge: ( themeId: string, isLockedStyleVariation: boolean ) => React.ReactNode;
oldHighResImageLoading?: boolean; // Temporary for A/B test
isSiteAssemblerEnabled?: boolean; // Temporary for A/B test
+ siteActiveTheme?: string | null;
+ showActiveThemeBadge?: boolean;
}
const UnifiedDesignPicker: React.FC< UnifiedDesignPickerProps > = ( {
@@ -355,6 +372,8 @@ const UnifiedDesignPicker: React.FC< UnifiedDesignPickerProps > = ( {
getBadge,
oldHighResImageLoading,
isSiteAssemblerEnabled,
+ siteActiveTheme = null,
+ showActiveThemeBadge = false,
} ) => {
const hasCategories = !! Object.keys( categorization?.categories || {} ).length;
@@ -389,6 +408,8 @@ const UnifiedDesignPicker: React.FC< UnifiedDesignPickerProps > = ( {
getBadge={ getBadge }
oldHighResImageLoading={ oldHighResImageLoading }
isSiteAssemblerEnabled={ isSiteAssemblerEnabled }
+ siteActiveTheme={ siteActiveTheme }
+ showActiveThemeBadge={ showActiveThemeBadge }
/>
{ bottomAnchorContent }
diff --git a/packages/design-picker/src/index.ts b/packages/design-picker/src/index.ts
index 4265681ecdf791..0b65e462c6e192 100644
--- a/packages/design-picker/src/index.ts
+++ b/packages/design-picker/src/index.ts
@@ -1,6 +1,7 @@
export { default } from './components';
export { default as themesIllustrationImage } from './components/assets/images/themes-illustration.png';
export { default as assemblerIllustrationImage } from './components/assets/images/assembler-illustration.png';
+export { default as assemblerIllustrationV2Image } from './components/assets/images/assembler-illustration-v2.png';
export { default as hiBigSky } from './components/assets/images/hi-big-sky.png';
export { default as bigSkyModalHeader } from './components/assets/images/big-sky-interstitial-800.png';
export { default as FeaturedPicksButtons } from './components/featured-picks-buttons';
diff --git a/packages/help-center/src/components/help-center-container.tsx b/packages/help-center/src/components/help-center-container.tsx
index 27c782aafa5210..c9fce5a6968f1e 100644
--- a/packages/help-center/src/components/help-center-container.tsx
+++ b/packages/help-center/src/components/help-center-container.tsx
@@ -59,12 +59,6 @@ const HelpCenterContainer: React.FC< Container > = ( {
recordTracksEvent( `calypso_inlinehelp_close` );
}, [ handleClose ] );
- const animationProps = {
- style: {
- ...openingCoordinates,
- },
- };
-
const focusReturnRef = useFocusReturn();
const cardMergeRefs = useMergeRefs( [ nodeRef, focusReturnRef ] );
@@ -99,7 +93,7 @@ const HelpCenterContainer: React.FC< Container > = ( {
handle=".help-center__container-header"
bounds="body"
>
-
+
setIsMinimized( true ) }
diff --git a/packages/help-center/src/components/help-center-header.scss b/packages/help-center/src/components/help-center-header.scss
index 3c6a417084d927..24dea0f3d70e16 100644
--- a/packages/help-center/src/components/help-center-header.scss
+++ b/packages/help-center/src/components/help-center-header.scss
@@ -59,7 +59,7 @@
display: inline-block;
margin-left: 8px;
padding: 2px 8px;
- background: var(--studio-pink-50);
+ background: var(--color-masterbar-unread-dot-background);
border-radius: 50%;
font-size: $font-body-extra-small;
color: #fff;
diff --git a/packages/help-center/src/components/help-center-more-resources.scss b/packages/help-center/src/components/help-center-more-resources.scss
index 412df9c6e3f346..4c66331926a464 100644
--- a/packages/help-center/src/components/help-center-more-resources.scss
+++ b/packages/help-center/src/components/help-center-more-resources.scss
@@ -11,7 +11,7 @@
button {
&.help-center-more-resources__institution {
> svg:first-child {
- fill: var(--studio-pink-50);
+ fill: var(--studio-green);
}
}
diff --git a/packages/help-center/src/hooks/use-context-based-search-mapping.tsx b/packages/help-center/src/hooks/use-context-based-search-mapping.tsx
index bcc1fc3bc1bea1..e317e5c5dfc8b1 100644
--- a/packages/help-center/src/hooks/use-context-based-search-mapping.tsx
+++ b/packages/help-center/src/hooks/use-context-based-search-mapping.tsx
@@ -1,5 +1,5 @@
import { useSelect } from '@wordpress/data';
-import urlMapping from '../route-to-query-mapping.json';
+import { useQueryForRoute } from '../route-to-query-mapping';
interface CoreBlockEditor {
getSelectedBlock: () => object;
@@ -25,15 +25,7 @@ export function useContextBasedSearchMapping( currentRoute: string | undefined )
return '';
}, [] );
- // Fuzzier matches
- const urlMatchKey = Object.keys( urlMapping ).find( ( key ) => currentRoute?.startsWith( key ) );
- const urlSearchQuery = urlMatchKey ? urlMapping[ urlMatchKey as keyof typeof urlMapping ] : '';
-
- // Find exact URL matches
- const exactMatch = urlMapping[ currentRoute as keyof typeof urlMapping ];
- if ( exactMatch ) {
- return { contextSearch: exactMatch };
- }
+ const urlSearchQuery = useQueryForRoute( currentRoute ?? '' );
return {
contextSearch: blockSearchQuery || urlSearchQuery || '',
diff --git a/packages/help-center/src/route-to-query-mapping.json b/packages/help-center/src/route-to-query-mapping.json
deleted file mode 100644
index e564837045be8e..00000000000000
--- a/packages/help-center/src/route-to-query-mapping.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "/add-ons/": "add-ons",
- "/comments/": "comments",
- "/plugins/manage": "manage plugins",
- "/plugins": "plugins",
- "/plans/": "upgrade plan",
- "/email/": "manage emails",
- "/woocommerce": "woocommerce",
- "/wp-admin/admin.php?page=wc": "woocommerce",
- "/subscribers": "subscribers",
- "/me/privacy": "privacy",
- "/me/notifications": "notification settings",
- "/me/site-blocks": "blocked sites",
- "/me/get-apps": "wordpress apps",
- "/settings/writing/": "writing settings",
- "/settings/reading/": "reading settings",
- "/settings/performance/": "performance settings",
- "/settings/taxonomies/category/": "site categories",
- "/settings/taxonomies/post_tag/": "post tag",
- "/settings/podcasting/": "podcasting",
- "/hosting-config/": "hosting configuration",
- "/wp-admin/options-media.php": "media settings",
- "/wp-admin/edit.php?post_type=jetpack-testimonial": "testimonials",
- "/wp-admin/edit.php?post_type=feedback": "feedback form",
- "/wp-admin/post-new.php?post_type=jetpack-testimonial": "new testimonial",
- "/wp-admin/admin.php?page=akismet-key-config": "site spam",
- "/wp-admin/admin.php?page=jetpack-search": "jetpack search",
- "/wp-admin/admin.php?page=polls": "crowdsignal",
- "/wp-admin/admin.php?page=ratings": "ratings",
- "/wp-admin/options-general.php?page=debug-bar-extender": "debug bar extender",
- "/wp-admin/index.php?page=my-blogs": "my sites",
- "/read/conversations": "conversations",
- "/read/notifications": "notifications",
- "/read/subscriptions": "manage subscriptions",
- "/read/list": "reader list",
- "/read/search": "search",
- "/read": "reader",
- "/discover": "discover blogs",
- "/tags": "tags",
- "/sites": "manage sites",
- "/marketing/sharing-buttons/": "social share",
- "/marketing/business-tools/": "business tools",
- "/advertising/": "advertising"
-}
diff --git a/packages/help-center/src/route-to-query-mapping.ts b/packages/help-center/src/route-to-query-mapping.ts
new file mode 100644
index 00000000000000..1bc33893436e6b
--- /dev/null
+++ b/packages/help-center/src/route-to-query-mapping.ts
@@ -0,0 +1,58 @@
+import { __ } from '@wordpress/i18n';
+
+export const useQueryForRoute = ( currentRoute: string ) => {
+ const urlMapping = {
+ '/add-ons/': __( 'add-ons' ),
+ '/advertising/': __( 'advertising' ),
+ '/comments/': __( 'comments' ),
+ '/discover': __( 'discover blogs' ),
+ '/email/': __( 'manage emails' ),
+ '/hosting-config/': __( 'hosting configuration' ),
+ '/marketing/business-tools/': __( 'business tools' ),
+ '/marketing/sharing-buttons/': __( 'social share' ),
+ '/me/get-apps': __( 'wordpress apps' ),
+ '/me/notifications': __( 'notification settings' ),
+ '/me/privacy': __( 'privacy' ),
+ '/me/site-blocks': __( 'blocked sites' ),
+ '/plans/': __( 'upgrade plan' ),
+ '/plugins': __( 'plugins' ),
+ '/plugins/manage': __( 'manage plugins' ),
+ '/read': __( 'reader' ),
+ '/read/conversations': __( 'conversations' ),
+ '/read/list': __( 'reader list' ),
+ '/read/notifications': __( 'notifications' ),
+ '/read/search': __( 'search' ),
+ '/read/subscriptions': __( 'manage subscriptions' ),
+ '/settings/performance/': __( 'performance settings' ),
+ '/settings/podcasting/': __( 'podcasting' ),
+ '/settings/reading/': __( 'reading settings' ),
+ '/settings/taxonomies/category/': __( 'site categories' ),
+ '/settings/taxonomies/post_tag/': __( 'post tag' ),
+ '/settings/writing/': __( 'writing settings' ),
+ '/sites': __( 'manage sites' ),
+ '/subscribers': __( 'subscribers' ),
+ '/tags': __( 'tags' ),
+ '/woocommerce': __( 'woocommerce' ),
+ '/wp-admin/admin.php?page=akismet-key-config': __( 'site spam' ),
+ '/wp-admin/admin.php?page=jetpack-search': __( 'jetpack search' ),
+ '/wp-admin/admin.php?page=polls': __( 'crowdsignal' ),
+ '/wp-admin/admin.php?page=ratings': __( 'ratings' ),
+ '/wp-admin/admin.php?page=wc': __( 'woocommerce' ),
+ '/wp-admin/edit.php?post_type=feedback': __( 'feedback form' ),
+ '/wp-admin/edit.php?post_type=jetpack-testimonial': __( 'testimonials' ),
+ '/wp-admin/index.php?page=my-blogs': __( 'my sites' ),
+ '/wp-admin/options-general.php?page=debug-bar-extender': __( 'debug bar extender' ),
+ '/wp-admin/options-media.php': __( 'media settings' ),
+ '/wp-admin/post-new.php?post_type=jetpack-testimonial': __( 'new testimonial' ),
+ };
+
+ // Find exact URL matches
+ const exactMatch = urlMapping[ currentRoute as keyof typeof urlMapping ];
+ if ( exactMatch ) {
+ return exactMatch;
+ }
+
+ // Fuzzier matches
+ const urlMatchKey = Object.keys( urlMapping ).find( ( key ) => currentRoute?.startsWith( key ) );
+ return urlMatchKey ? urlMapping[ urlMatchKey as keyof typeof urlMapping ] : '';
+};
diff --git a/packages/odie-client/src/components/send-message-input/style.scss b/packages/odie-client/src/components/send-message-input/style.scss
index 2f725338917916..afccba74346488 100644
--- a/packages/odie-client/src/components/send-message-input/style.scss
+++ b/packages/odie-client/src/components/send-message-input/style.scss
@@ -26,6 +26,10 @@
top: 20px;
}
+.odie-send-message-input-container textarea {
+ font-family: inherit;
+}
+
.odie-send-message-input-container .odie-send-message-inner-button {
background: none;
border: none;
diff --git a/packages/onboarding/src/cart/index.tsx b/packages/onboarding/src/cart/index.tsx
index 7fe611ee712e1e..b0cf608ce03030 100644
--- a/packages/onboarding/src/cart/index.tsx
+++ b/packages/onboarding/src/cart/index.tsx
@@ -144,8 +144,8 @@ export const createSiteWithCart = async (
siteAccentColor: string,
useThemeHeadstart: boolean,
username: string,
+ domainCartItems: MinimalRequestCartProduct[],
domainItem?: DomainSuggestion,
- domainCartItem?: MinimalRequestCartProduct,
sourceSlug?: string
) => {
const siteUrl = domainItem?.domain_name;
@@ -213,14 +213,18 @@ export const createSiteWithCart = async (
await setupSiteAfterCreation( { siteId, flowName } );
}
- await processItemCart(
- siteSlug,
- isFreeThemePreselected,
- themeSlugWithRepo,
- flowName,
- userIsLoggedIn,
- domainCartItem
- );
+ if ( domainCartItems.length ) {
+ for ( const domainCartItem of domainCartItems ) {
+ await processItemCart(
+ siteSlug,
+ isFreeThemePreselected,
+ themeSlugWithRepo,
+ flowName,
+ userIsLoggedIn,
+ domainCartItem
+ );
+ }
+ }
return providedDependencies;
};
diff --git a/packages/onboarding/src/step-container/index.tsx b/packages/onboarding/src/step-container/index.tsx
index 7f648529c59ba3..37ae351ab5cfc1 100644
--- a/packages/onboarding/src/step-container/index.tsx
+++ b/packages/onboarding/src/step-container/index.tsx
@@ -47,6 +47,7 @@ interface Props {
showFooterWooCommercePowered?: boolean;
showSenseiPowered?: boolean;
showVideoPressPowered?: boolean;
+ backUrl?: string;
}
const StepContainer: React.FC< Props > = ( {
@@ -75,6 +76,7 @@ const StepContainer: React.FC< Props > = ( {
isExternalBackUrl,
isLargeSkipLayout,
customizedActionButtons,
+ backUrl,
goBack,
goNext,
flowName,
@@ -109,13 +111,14 @@ const StepContainer: React.FC< Props > = ( {
function BackButton() {
// Hide back button if goBack is falsy, it won't do anything in that case.
- if ( shouldHideNavButtons || ! goBack ) {
+ if ( shouldHideNavButtons || ( ! goBack && ! backUrl ) ) {
return null;
}
return (
void;
}
@@ -27,6 +28,7 @@ const StepNavigationLink: React.FC< Props > = ( {
cssClass,
rel,
recordClick,
+ backUrl,
} ) => {
const translate = useTranslate();
@@ -59,6 +61,7 @@ const StepNavigationLink: React.FC< Props > = ( {
borderless={ borderless }
className={ buttonClasses }
onClick={ onClick }
+ href={ backUrl }
rel={ rel }
>
{ backGridicon }
diff --git a/packages/page-pattern-modal/package.json b/packages/page-pattern-modal/package.json
index fe217d95648013..347ad6376d2336 100644
--- a/packages/page-pattern-modal/package.json
+++ b/packages/page-pattern-modal/package.json
@@ -27,7 +27,7 @@
],
"types": "dist/types",
"dependencies": {
- "@automattic/color-studio": "2.6.0",
+ "@automattic/color-studio": "^3.0.1",
"@automattic/typography": "1.0.0",
"@wordpress/base-styles": "5.2.0",
"@wordpress/block-editor": "^13.2.0",
diff --git a/packages/plans-grid-next/src/components/shared/header-price/index.tsx b/packages/plans-grid-next/src/components/shared/header-price/index.tsx
index 45006ecfab3108..d33f344b935045 100644
--- a/packages/plans-grid-next/src/components/shared/header-price/index.tsx
+++ b/packages/plans-grid-next/src/components/shared/header-price/index.tsx
@@ -39,7 +39,11 @@ const HeaderPrice = ( { planSlug, visibleGridPlans }: HeaderPriceProps ) => {
);
const { prices } = usePlanPricingInfoFromGridPlans( { gridPlans: visibleGridPlans } );
- const isLargeCurrency = useIsLargeCurrency( { prices, currencyCode: currencyCode || 'USD' } );
+ const isLargeCurrency = useIsLargeCurrency( {
+ prices,
+ currencyCode: currencyCode || 'USD',
+ ignoreWhitespace: true,
+ } );
if ( isWpcomEnterpriseGridPlan( planSlug ) || ! isPricedPlan ) {
return null;
diff --git a/packages/plans-grid-next/src/hooks/data-store/types.ts b/packages/plans-grid-next/src/hooks/data-store/types.ts
index 387929966d5d52..e521bbe667e669 100644
--- a/packages/plans-grid-next/src/hooks/data-store/types.ts
+++ b/packages/plans-grid-next/src/hooks/data-store/types.ts
@@ -17,7 +17,6 @@ export interface UseGridPlansParams {
selectedFeature?: string | null;
selectedPlan?: PlanSlug;
showLegacyStorageFeature?: boolean;
- forceDefaultIntent?: boolean;
siteId?: number | null;
storageAddOns: ( AddOnMeta | null )[];
term?: ( typeof TERMS_LIST )[ number ]; // defaults to monthly
diff --git a/packages/plans-grid-next/src/hooks/data-store/use-grid-plans-for-comparison-grid.ts b/packages/plans-grid-next/src/hooks/data-store/use-grid-plans-for-comparison-grid.ts
index 528304910dd3d6..ae05b1aaa38a8d 100644
--- a/packages/plans-grid-next/src/hooks/data-store/use-grid-plans-for-comparison-grid.ts
+++ b/packages/plans-grid-next/src/hooks/data-store/use-grid-plans-for-comparison-grid.ts
@@ -27,7 +27,6 @@ const useGridPlansForComparisonGrid = ( {
term,
useCheckPlanAvailabilityForPurchase,
useFreeTrialPlanSlugs,
- forceDefaultIntent,
}: UseGridPlansParams ): GridPlan[] | null => {
const gridPlans = useGridPlans( {
allFeaturesList,
@@ -45,7 +44,6 @@ const useGridPlansForComparisonGrid = ( {
term,
useCheckPlanAvailabilityForPurchase,
useFreeTrialPlanSlugs,
- forceDefaultIntent,
} );
const planFeaturesForComparisonGrid = useRestructuredPlanFeaturesForComparisonGrid( {
diff --git a/packages/plans-grid-next/src/hooks/data-store/use-grid-plans.tsx b/packages/plans-grid-next/src/hooks/data-store/use-grid-plans.tsx
index be3df279d29fc4..84b6ab18454d99 100644
--- a/packages/plans-grid-next/src/hooks/data-store/use-grid-plans.tsx
+++ b/packages/plans-grid-next/src/hooks/data-store/use-grid-plans.tsx
@@ -228,7 +228,6 @@ const useGridPlans: UseGridPlansType = ( {
coupon,
siteId,
isDisplayingPlansNeededForFeature,
- forceDefaultIntent,
highlightLabelOverrides,
} ) => {
const freeTrialPlanSlugs = useFreeTrialPlanSlugs?.( {
@@ -248,7 +247,7 @@ const useGridPlans: UseGridPlansType = ( {
} );
const planSlugsForIntent = usePlansFromTypes( {
planTypes: usePlanTypesWithIntent( {
- intent: forceDefaultIntent ? 'plans-default-wpcom' : intent,
+ intent,
selectedPlan,
siteId,
hiddenPlans,
diff --git a/packages/plans-grid-next/src/hooks/use-is-large-currency.ts b/packages/plans-grid-next/src/hooks/use-is-large-currency.ts
index 98524ec04086e5..e2428c1f63c460 100644
--- a/packages/plans-grid-next/src/hooks/use-is-large-currency.ts
+++ b/packages/plans-grid-next/src/hooks/use-is-large-currency.ts
@@ -9,9 +9,10 @@ interface Props {
prices?: number[];
isAddOn?: boolean;
currencyCode: string;
+ ignoreWhitespace?: boolean;
}
-function useDisplayPrices( currencyCode: string, prices?: number[] ) {
+function useDisplayPrices( currencyCode: string, prices?: number[], ignoreWhitespace = false ) {
/**
* Prices are represented in smallest units for a currency, and not as prices that
* are actually displayed. Ex. $20 is the integer 2000, and not 20. To determine if
@@ -20,13 +21,15 @@ function useDisplayPrices( currencyCode: string, prices?: number[] ) {
return useMemo(
() =>
- prices?.map( ( price ) =>
- formatCurrency( price, currencyCode, {
+ prices?.map( ( price ) => {
+ const displayPrice = formatCurrency( price, currencyCode, {
stripZeros: true,
isSmallestUnit: true,
- } )
- ),
- [ currencyCode, prices ]
+ } );
+
+ return ignoreWhitespace ? displayPrice.replace( /\s/g, '' ) : displayPrice;
+ } ),
+ [ currencyCode, prices, ignoreWhitespace ]
);
}
@@ -64,7 +67,12 @@ function hasExceededCombinedPriceThreshold( displayPrices?: string[] ) {
* 9 characters. For example, $4,000 undiscounted and $30 discounted would be 9 characters.
* This is primarily used for lowering the font-size of "large" display prices.
*/
-export default function useIsLargeCurrency( { prices, isAddOn = false, currencyCode }: Props ) {
+export default function useIsLargeCurrency( {
+ prices,
+ isAddOn = false,
+ currencyCode,
+ ignoreWhitespace = false,
+}: Props ) {
/**
* Because this hook is primarily used for lowering font-sizes of "large" display prices,
* this implementation is non-ideal. It assumes that each character in the display price,
@@ -76,7 +84,7 @@ export default function useIsLargeCurrency( { prices, isAddOn = false, currencyC
*
* https://github.com/Automattic/wp-calypso/pull/81537#discussion_r1323182287
*/
- const displayPrices = useDisplayPrices( currencyCode, prices );
+ const displayPrices = useDisplayPrices( currencyCode, prices, ignoreWhitespace );
const exceedsPriceThreshold = hasExceededPriceThreshold( displayPrices, isAddOn );
const exceedsCombinedPriceThreshold = hasExceededCombinedPriceThreshold( displayPrices );
diff --git a/packages/urls/src/index.ts b/packages/urls/src/index.ts
index 9f49f395349123..96440e9da05f32 100644
--- a/packages/urls/src/index.ts
+++ b/packages/urls/src/index.ts
@@ -25,7 +25,7 @@ export const DNS_RECORDS_EDITING_OR_DELETING = `${ root }/domains/custom-dns/#ed
export const DNS_TXT_RECORD_CHAR_LIMIT = `${ root }/domains/custom-dns/#txt-record-character-limit`;
export const ECOMMERCE = `${ root }/ecommerce/`;
export const INCOMING_DOMAIN_TRANSFER_STATUSES = `${ root }/move-domain/incoming-domain-transfer/#checking-your-transfer-status-and-failed-transfers`;
-export const INCOMING_DOMAIN_TRANSFER_STATUSES_IN_PROGRESS = `${ root }/incoming-domain-transfer/status-and-failed-transfers/#pending`;
+export const INCOMING_DOMAIN_TRANSFER_STATUSES_IN_PROGRESS = `${ root }/incoming-domain-transfer/#step-4-check-the-transfer-status`;
export const INCOMING_DOMAIN_TRANSFER = `${ root }/incoming-domain-transfer/`;
export const INCOMING_DOMAIN_TRANSFER_PREPARE_UNLOCK = `${ root }/incoming-domain-transfer/#step-1-unlock-your-domain`;
export const INCOMING_DOMAIN_TRANSFER_PREPARE_AUTH_CODE = `${ root }/incoming-domain-transfer/#step-2-obtain-your-domain-transfer-authorization-code`;
diff --git a/packages/wpcom-checkout/src/checkout-labels.ts b/packages/wpcom-checkout/src/checkout-labels.ts
index a3ae923758bf19..ae79b469bba665 100644
--- a/packages/wpcom-checkout/src/checkout-labels.ts
+++ b/packages/wpcom-checkout/src/checkout-labels.ts
@@ -96,13 +96,10 @@ export function getLabel( product: ResponseCartProduct ): string {
return product.meta;
}
- if (
- isJetpackAISlug( product.product_slug ) &&
- ( product.quantity !== null || product.current_quantity !== null )
- ) {
- // In theory, it'll fallback to 0, but just in case.
- const quantity = product.quantity || product.current_quantity || 0;
+ // In theory, it'll fallback to 0, but just in case.
+ const quantity = product.quantity || product.current_quantity || 0;
+ if ( isJetpackAISlug( product.product_slug ) && quantity > 1 ) {
return translate( '%(productName)s (%(quantity)d requests per month)', {
args: {
productName: product.product_name,
diff --git a/packages/wpcom-checkout/src/payment-method-logos.tsx b/packages/wpcom-checkout/src/payment-method-logos.tsx
index af7c7084d9c1b4..47a65d0ee1381e 100644
--- a/packages/wpcom-checkout/src/payment-method-logos.tsx
+++ b/packages/wpcom-checkout/src/payment-method-logos.tsx
@@ -4,14 +4,21 @@ import styled from '@emotion/styled';
/* eslint-disable @typescript-eslint/no-use-before-define */
export const PaymentMethodLogos = styled.span`
+ display: flex;
+ flex: 1;
text-align: right;
- transform: translateY( 3px );
+ align-items: center;
+ justify-content: flex-end;
+
.rtl & {
text-align: left;
}
svg {
- display: block;
+ display: inline-block;
+ &.has-background {
+ padding-inline-end: 5px;
+ }
}
&.google-pay__logo svg {
@@ -30,7 +37,6 @@ export function PaymentLogo( { brand, isSummary }: { brand: string; isSummary?:
);
break;
- case 'cb':
case 'cartes_bancaires':
cardFieldIcon = (
@@ -162,44 +168,65 @@ export function VisaLogo( { className }: { className?: string } ) {
}
export function CBLogo( { className }: { className?: string } ) {
+ // We need to provide a unique ID to any svg that uses an id prop
+ // especially if we expect multiple instances of the component to render on the page
+ const uniqueID = `${ Math.floor( 10000 + Math.random() * 90000 ) }`;
+
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/packages/wpcom-template-parts/src/universal-header-navigation/style.scss b/packages/wpcom-template-parts/src/universal-header-navigation/style.scss
index 5b34ea6e34a913..ba836e1f7eb753 100644
--- a/packages/wpcom-template-parts/src/universal-header-navigation/style.scss
+++ b/packages/wpcom-template-parts/src/universal-header-navigation/style.scss
@@ -889,43 +889,3 @@ $x-menu-heading-line-height-wide: $x-menu-heading-font-size-wide + 7px; /* 1 */
}
/* End site profiler section overrides */
-
-/* Start start with section overrides */
-
-.is-style-white {
- .x-root {
- .masterbar-menu {
- .masterbar {
- border-bottom: none;
- background: #fdfdfd;
- height: 56px;
- position: relative;
- .x-nav {
- height: 56px;
- }
- }
- }
- }
-
- .x-nav-item {
- color: #000;
-
- .cta-btn-nav {
- border-radius: 4px;
- background: inherit !important;
- border: 1px solid #000;
- }
-
- .x-nav-link__chevron::after,
- .x-nav-link-chevron::before {
- content: $lp-chevron-content-down;
- color: #000;
- }
-
- .x-nav-link.x-nav-link.x-nav-link__primary {
- color: #000 !important;
- }
- }
-}
-
-/* End start with section overrides */
diff --git a/renovate.json5 b/renovate.json5
index b26ea154eb7ebe..dcd5e1d340edd2 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -77,12 +77,7 @@
enabled: false,
},
],
- ignoreDeps: [
- 'electron-builder',
- // We're intentionally locking to v0.4.1, see https://github.com/Automattic/wp-calypso/pull/87956
- // @TODO: Remove once updated to use the latest version
- '@wordpress/dataviews',
- ],
+ ignoreDeps: [ 'electron-builder' ],
regexManagers: [
// Update the renovate-version in the action itself.
// See also https://github.com/renovatebot/github-action/issues/756
diff --git a/test/e2e/specs/jetpack/jetpack__dashboard-smoke.ts b/test/e2e/specs/jetpack/jetpack__dashboard-smoke.ts
index 2ad3a690db76fb..8323ade220b40c 100644
--- a/test/e2e/specs/jetpack/jetpack__dashboard-smoke.ts
+++ b/test/e2e/specs/jetpack/jetpack__dashboard-smoke.ts
@@ -47,6 +47,16 @@ skipDescribeIf( envVariables.TEST_ON_ATOMIC !== true )(
}
jetpackDashboardPage = new JetpackDashboardPage( page );
+
+ // Atomic tests sites might have local users, so the Jetpack SSO login will
+ // show up when visiting the Jetpack dashboard directly. We can bypass it if
+ // we simulate a redirect from Calypso to WP Admin with a hardcoded referer.
+ // @see https://github.com/Automattic/jetpack/blob/12b3b9a4771169398d4e1982573aaec820babc17/projects/plugins/wpcomsh/wpcomsh.php#L230-L254
+ const siteUrl = testAccount.getSiteURL( { protocol: true } );
+ await page.goto( `${ siteUrl }wp-admin/`, {
+ timeout: 15 * 1000,
+ referer: 'https://wordpress.com/',
+ } );
} );
it( 'Navigate to Jetpack dashboard', async function () {
diff --git a/test/e2e/specs/martech/tos-screenshots__checkout.ts b/test/e2e/specs/martech/tos-screenshots__checkout.ts
index f1d607fdbf8c56..e0cd3e5b26bfec 100644
--- a/test/e2e/specs/martech/tos-screenshots__checkout.ts
+++ b/test/e2e/specs/martech/tos-screenshots__checkout.ts
@@ -33,6 +33,10 @@ describe( DataHelper.createSuiteTitle( 'ToS acceptance tracking screenshots' ),
await page.reload( { waitUntil: 'domcontentloaded', timeout: EXTENDED_TIMEOUT } );
} );
+ it( 'See Home', async function () {
+ await page.waitForURL( /home/ );
+ } );
+
it( 'Add WordPress.com Business plan to cart', async function () {
await Promise.all( [
page.waitForURL( /.*checkout.*/ ),
diff --git a/test/e2e/specs/published-content/forms__submissions.ts b/test/e2e/specs/published-content/forms__submissions.ts
index 26667ea3af8b7c..ee118eea01dc41 100644
--- a/test/e2e/specs/published-content/forms__submissions.ts
+++ b/test/e2e/specs/published-content/forms__submissions.ts
@@ -115,6 +115,18 @@ describe( DataHelper.createSuiteTitle( 'Feedback: Form Submission' ), function (
} else {
await testAccount.authenticate( page );
}
+
+ // Atomic tests sites might have local users, so the Jetpack SSO login will
+ // show up when visiting the Jetpack dashboard directly. We can bypass it if
+ // we simulate a redirect from Calypso to WP Admin with a hardcoded referer.
+ // @see https://github.com/Automattic/jetpack/blob/12b3b9a4771169398d4e1982573aaec820babc17/projects/plugins/wpcomsh/wpcomsh.php#L230-L254
+ if ( envVariables.TEST_ON_ATOMIC ) {
+ const siteUrl = testAccount.getSiteURL( { protocol: true } );
+ await page.goto( `${ siteUrl }wp-admin/`, {
+ timeout: 15 * 1000,
+ referer: 'https://wordpress.com/',
+ } );
+ }
} );
it( 'Navigate to the Jetpack Forms Inbox', async function () {
diff --git a/yarn.lock b/yarn.lock
index 6338854bbe8aa2..33b028d55b208a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -63,6 +63,13 @@ __metadata:
languageName: node
linkType: hard
+"@ariakit/core@npm:0.4.9":
+ version: 0.4.9
+ resolution: "@ariakit/core@npm:0.4.9"
+ checksum: 17a2b95804b3d3b1d7c6bc6aeef25dc300b3d7303e700c70878cfaea203105a5f1a0b5c67b7997d911cda0ba5895783ef2d3b5b83268a4e24d504eb67fa25fec
+ languageName: node
+ linkType: hard
+
"@ariakit/react-core@npm:0.3.14":
version: 0.3.14
resolution: "@ariakit/react-core@npm:0.3.14"
@@ -77,6 +84,20 @@ __metadata:
languageName: node
linkType: hard
+"@ariakit/react-core@npm:0.4.10":
+ version: 0.4.10
+ resolution: "@ariakit/react-core@npm:0.4.10"
+ dependencies:
+ "@ariakit/core": "npm:0.4.9"
+ "@floating-ui/dom": "npm:^1.0.0"
+ use-sync-external-store: "npm:^1.2.0"
+ peerDependencies:
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
+ checksum: eeb51c643ad36af8a293cc0fe3267214780ce9e97811dc44e69166f4592282efe0fd452bf12fee09a74f8deacce73a4c8d1859d0c11fbb4ca2b1a3dde2c7e708
+ languageName: node
+ linkType: hard
+
"@ariakit/react@npm:^0.3.12":
version: 0.3.14
resolution: "@ariakit/react@npm:0.3.14"
@@ -89,6 +110,18 @@ __metadata:
languageName: node
linkType: hard
+"@ariakit/react@npm:^0.4.10":
+ version: 0.4.10
+ resolution: "@ariakit/react@npm:0.4.10"
+ dependencies:
+ "@ariakit/react-core": "npm:0.4.10"
+ peerDependencies:
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
+ checksum: ed70c8b25694cec6022cd9fff87ef1ced57f75803df2db2c362a38f3dc15fb345cdd60b6c63aa7d305cef489ff2a49ef964cee9d5d518f09319d169a071a8190
+ languageName: node
+ linkType: hard
+
"@automattic/accessible-focus@workspace:^, @automattic/accessible-focus@workspace:packages/accessible-focus":
version: 0.0.0-use.local
resolution: "@automattic/accessible-focus@workspace:packages/accessible-focus"
@@ -322,7 +355,7 @@ __metadata:
dependencies:
"@automattic/calypso-eslint-overrides": "workspace:^"
"@automattic/calypso-typescript-config": "workspace:^"
- "@automattic/color-studio": "npm:2.6.0"
+ "@automattic/color-studio": "npm:^3.0.1"
postcss: "npm:^8.4.5"
postcss-custom-properties: "npm:^11.0.0"
sass: "npm:^1.37.5"
@@ -545,10 +578,10 @@ __metadata:
languageName: unknown
linkType: soft
-"@automattic/color-studio@npm:2.6.0":
- version: 2.6.0
- resolution: "@automattic/color-studio@npm:2.6.0"
- checksum: 1171df1d9b92b2734950239eb82250fe53f45d09485fd18b2aa62a40aaabbaa1c127898da97319f871e137254aae9ed7141e2aae30ec09f0151f515af8516510
+"@automattic/color-studio@npm:^3.0.1":
+ version: 3.0.1
+ resolution: "@automattic/color-studio@npm:3.0.1"
+ checksum: 1fed9e35e5a4cf283055d323661bf94f8836fbe762c3a1ff7c514407c9cffcae9b05791fb2d4904050c097e939b745c8f2bc13cc84c9963d35c9d1aa57f1fbd8
languageName: node
linkType: hard
@@ -673,7 +706,7 @@ __metadata:
dependencies:
"@automattic/calypso-storybook": "workspace:^"
"@automattic/calypso-typescript-config": "workspace:^"
- "@automattic/color-studio": "npm:2.6.0"
+ "@automattic/color-studio": "npm:^3.0.1"
"@emotion/react": "npm:^11.11.1"
"@emotion/styled": "npm:^11.11.0"
"@storybook/cli": "npm:^7.6.19"
@@ -1592,7 +1625,7 @@ __metadata:
resolution: "@automattic/page-pattern-modal@workspace:packages/page-pattern-modal"
dependencies:
"@automattic/calypso-typescript-config": "workspace:^"
- "@automattic/color-studio": "npm:2.6.0"
+ "@automattic/color-studio": "npm:^3.0.1"
"@automattic/typography": "npm:1.0.0"
"@testing-library/react": "npm:^15.0.7"
"@wordpress/base-styles": "npm:5.2.0"
@@ -9676,47 +9709,26 @@ __metadata:
languageName: node
linkType: hard
-"@wordpress/dataviews@npm:0.4.1":
- version: 0.4.1
- resolution: "@wordpress/dataviews@npm:0.4.1"
- dependencies:
- "@babel/runtime": "npm:^7.16.0"
- "@wordpress/a11y": "npm:^3.50.0"
- "@wordpress/components": "npm:^25.16.0"
- "@wordpress/compose": "npm:^6.27.0"
- "@wordpress/element": "npm:^5.27.0"
- "@wordpress/i18n": "npm:^4.50.0"
- "@wordpress/icons": "npm:^9.41.0"
- "@wordpress/keycodes": "npm:^3.50.0"
- "@wordpress/primitives": "npm:^3.48.0"
- "@wordpress/private-apis": "npm:^0.32.0"
- classnames: "npm:^2.3.1"
- remove-accents: "npm:^0.5.0"
- peerDependencies:
- react: ^18.0.0
- checksum: 00f5be7dc18de659bb52587380d5d88f0eda5fa99309bcc16bb02b9a4a7a51c82654bbf69aa0f7831e8f64df3e5c378d121874b795b7d3d60155d73a0f5adc1b
- languageName: node
- linkType: hard
-
-"@wordpress/dataviews@patch:@wordpress/dataviews@npm%3A0.4.1#~/.yarn/patches/@wordpress-dataviews-npm-0.4.1-2c01fa0792.patch":
- version: 0.4.1
- resolution: "@wordpress/dataviews@patch:@wordpress/dataviews@npm%3A0.4.1#~/.yarn/patches/@wordpress-dataviews-npm-0.4.1-2c01fa0792.patch::version=0.4.1&hash=d91dff"
+"@wordpress/dataviews@npm:4.2.0":
+ version: 4.2.0
+ resolution: "@wordpress/dataviews@npm:4.2.0"
dependencies:
+ "@ariakit/react": "npm:^0.4.10"
"@babel/runtime": "npm:^7.16.0"
- "@wordpress/a11y": "npm:^3.50.0"
- "@wordpress/components": "npm:^25.16.0"
- "@wordpress/compose": "npm:^6.27.0"
- "@wordpress/element": "npm:^5.27.0"
- "@wordpress/i18n": "npm:^4.50.0"
- "@wordpress/icons": "npm:^9.41.0"
- "@wordpress/keycodes": "npm:^3.50.0"
- "@wordpress/primitives": "npm:^3.48.0"
- "@wordpress/private-apis": "npm:^0.32.0"
- classnames: "npm:^2.3.1"
+ "@wordpress/components": "npm:^28.6.0"
+ "@wordpress/compose": "npm:^7.6.0"
+ "@wordpress/data": "npm:^10.6.0"
+ "@wordpress/element": "npm:^6.6.0"
+ "@wordpress/i18n": "npm:^5.6.0"
+ "@wordpress/icons": "npm:^10.6.0"
+ "@wordpress/primitives": "npm:^4.6.0"
+ "@wordpress/private-apis": "npm:^1.6.0"
+ "@wordpress/warning": "npm:^3.6.0"
+ clsx: "npm:^2.1.1"
remove-accents: "npm:^0.5.0"
peerDependencies:
react: ^18.0.0
- checksum: 757d69e92446b3d0a28b94c3cf2d3711a304c9949001392e2bb62f37409bee6561af5587df057a2296229f12b70210626d25db94280dfaa8b5f0789dd5131ddb
+ checksum: 01dbabaea48def0b810b5bc53012d89f3aa308fa44a6b5ac51a2537474f6f2ce513390ca9c4fdf9d0190f3fc9d3210d5ba0bb2fbb4862d9e3ac085d23dabe646
languageName: node
linkType: hard
@@ -9833,7 +9845,6 @@ __metadata:
"@wordpress/core-commands": "npm:^1.2.0"
"@wordpress/core-data": "npm:^7.2.0"
"@wordpress/data": "npm:^10.2.0"
- "@wordpress/dataviews": "npm:^2.2.0"
"@wordpress/date": "npm:^5.2.0"
"@wordpress/deprecated": "npm:^4.2.0"
"@wordpress/dom": "npm:^4.2.0"
@@ -12661,7 +12672,7 @@ __metadata:
"@automattic/calypso-sentry": "workspace:^"
"@automattic/calypso-stripe": "workspace:^"
"@automattic/calypso-url": "workspace:^"
- "@automattic/color-studio": "npm:2.6.0"
+ "@automattic/color-studio": "npm:^3.0.1"
"@automattic/command-palette": "workspace:^"
"@automattic/components": "workspace:^"
"@automattic/composite-checkout": "workspace:^"
@@ -12737,7 +12748,7 @@ __metadata:
"@wordpress/components": "npm:^28.2.0"
"@wordpress/compose": "npm:^7.2.0"
"@wordpress/data": "npm:^10.2.0"
- "@wordpress/dataviews": "patch:@wordpress/dataviews@npm%3A0.4.1#~/.yarn/patches/@wordpress-dataviews-npm-0.4.1-2c01fa0792.patch"
+ "@wordpress/dataviews": "npm:^4.2.0"
"@wordpress/dom": "npm:^4.2.0"
"@wordpress/edit-post": "npm:^8.2.0"
"@wordpress/element": "npm:^6.2.0"
@@ -13329,13 +13340,6 @@ __metadata:
languageName: node
linkType: hard
-"classnames@npm:^2.3.1":
- version: 2.3.2
- resolution: "classnames@npm:2.3.2"
- checksum: cd50ead57b4f97436aaa9f9885c6926323efc7c2bea8e3d4eb10e4e972aa6a1cfca1c7a0e06f8a199ca7498d4339e30bb6002e589e61c9f21248cbf3e8b0b18d
- languageName: node
- linkType: hard
-
"clean-css@npm:4.2.x":
version: 4.2.3
resolution: "clean-css@npm:4.2.3"
@@ -19287,7 +19291,7 @@ __metadata:
"@automattic/calypso-build": "workspace:^"
"@automattic/calypso-config": "workspace:^"
"@automattic/calypso-products": "workspace:^"
- "@automattic/color-studio": "npm:2.6.0"
+ "@automattic/color-studio": "npm:^3.0.1"
"@automattic/components": "workspace:^"
"@automattic/format-currency": "workspace:^"
"@automattic/typography": "workspace:^"
@@ -34502,7 +34506,7 @@ __metadata:
"@automattic/calypso-razorpay": "workspace:^"
"@automattic/calypso-router": "workspace:^"
"@automattic/calypso-storybook": "workspace:^"
- "@automattic/color-studio": "npm:2.6.0"
+ "@automattic/color-studio": "npm:^3.0.1"
"@automattic/command-palette": "workspace:^"
"@automattic/components": "workspace:^"
"@automattic/data-stores": "workspace:^"