Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add/fix critical css e2e on interstitial #42285

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
72792ff
My Jetpack: Updating Interstitial modal with a custom trigger and docs
grzegorz-cp Feb 7, 2025
72c0c0f
cleanup
grzegorz-cp Feb 7, 2025
3c70e57
Fix props name to display the correct button
IanRamosC Feb 19, 2025
3b72135
Use modal cta component (#41973)
IanRamosC Feb 25, 2025
4de3438
Fix documentation for clarity
IanRamosC Feb 25, 2025
4b27afe
Add My Jetpack as dependency to Jetpack Boost
IanRamosC Feb 26, 2025
9ec85db
Add temp Modal interstitial for Jetpack Boost
IanRamosC Feb 26, 2025
9f53847
changelog
IanRamosC Feb 26, 2025
2e96478
Fix product interstitial with query client
IanRamosC Feb 26, 2025
cea499a
Display modal for Boost on Critical CSS button click
IanRamosC Feb 26, 2025
1c5c565
changelog
IanRamosC Feb 26, 2025
908d622
Merge branch 'trunk' into add/boost-interstitial-modal-critical-css
IanRamosC Feb 27, 2025
ce2bbc5
Fix pricingForUi usage in the modal
IanRamosC Feb 28, 2025
83f7d80
Merge branch 'trunk' into add/boost-interstitial-modal-critical-css
grzegorz-cp Mar 4, 2025
fb5834c
Boost: updating modal with Boost specific price structure
grzegorz-cp Mar 5, 2025
71a572a
Merge branch 'trunk' into add/boost-interstitial-modal-critical-css
grzegorz-cp Mar 5, 2025
f5e22f2
Boost: Updating pricing to monthly values, using custom features and …
grzegorz-cp Mar 5, 2025
f460516
Merge branch 'trunk' into add/boost-interstitial-modal-critical-css
grzegorz-cp Mar 5, 2025
76705b7
Merge branch 'trunk' into add/boost-interstitial-modal-critical-css
grzegorz-cp Mar 6, 2025
a1bc1c9
Boost: Reducing feature list copy
grzegorz-cp Mar 6, 2025
3c38e9d
Boost: Reducing feature list copy
grzegorz-cp Mar 6, 2025
d351c79
Merge branch 'trunk' into add/boost-interstitial-modal-critical-css
dilirity Mar 6, 2025
2db1de6
Merge branch 'trunk' into add/boost-interstitial-modal-critical-css
IanRamosC Mar 6, 2025
676d7e2
Update event name when modal is open
IanRamosC Mar 6, 2025
6c5187e
Refactor UpgradeCTA + Modal for multiple usage in Boost
IanRamosC Mar 6, 2025
af43731
Remove onOpen event from the modal cta abstraction in boost
IanRamosC Mar 6, 2025
fd304b4
Boost: Fixing page element check for e2e tests
grzegorz-cp Mar 7, 2025
9e3c682
Show error in error-boundry
haqadn Mar 7, 2025
7f0d376
[Trial-Error] Don't useConnection in Upgrade
haqadn Mar 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const ProductInterstitialFeatureList: FC< ProductInterstitialFeatureListProps >
{ features.map( ( feature, id ) => (
<Text component="li" key={ `feature-${ id }` } variant="body">
<Icon icon={ check } size={ 24 } />
{ feature }
<span>{ feature }</span>
</Text>
) ) }
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ const ProductInterstitialModalCta: FC< ProductInterstitialModalCtaProps > = ( {
myJetpackCheckoutUri = '',
} = getMyJetpackWindowInitialState();

const { detail } = useProduct( slug );
const { detail, isLoading: isProductLoading } = useProduct( slug );

const { pricingForUi, postCheckoutUrl } = detail;

const { wpcomProductSlug } = pricingForUi;
const { wpcomProductSlug } = pricingForUi || {};

// Redirect to the referrer URL when the `redirect_to_referrer` query param is present.
const referrerURL = useRedirectToReferrer();
Expand Down Expand Up @@ -76,11 +76,11 @@ const ProductInterstitialModalCta: FC< ProductInterstitialModalCtaProps > = ( {
<Button
variant="primary"
className={ styles[ 'action-button' ] }
isLoading={ hasMainCheckoutStarted }
isLoading={ isProductLoading || hasMainCheckoutStarted }
onClick={ mainCheckoutRedirect }
isExternalLink={ isExternalLink }
href={ href }
disabled={ disabled }
disabled={ disabled || isProductLoading }
>
{ buttonLabel || __( 'Upgrade', 'jetpack-my-jetpack' ) }
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Button, ProductPrice, getRedirectUrl } from '@automattic/jetpack-components';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createInterpolateElement } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { useCallback, type FC } from 'react';
import useProduct from '../../data/products/use-product';
import useAnalytics from '../../hooks/use-analytics';
import LoadingBlock from '../loading-block';
import {
ProductInterstitialModal,
ProductInterstitialFeatureList,
Expand All @@ -27,6 +29,14 @@ interface ProductInterstitialPluginProps {
* Callback function to be called when the modal is closed
*/
onClose?: () => void;
/**
* Optional description for the product that overwrites the description from the product details
*/
description?: string;
/**
* Optional features for the product that overwrites the features from the product details
*/
features?: string[];
}

/**
Expand All @@ -40,20 +50,37 @@ const ProductInterstitialPlugin: FC< ProductInterstitialPluginProps > = ( {
children,
onOpen,
onClose,
description,
features,
...props
} ) => {
const { recordEvent } = useAnalytics();
const { detail } = useProduct( slug );
const { detail, isLoading } = useProduct( slug );
const {
title,
longDescription: detailDescription,
features: detailFeatures,
pricingForUi,
} = detail;

const { title, longDescription, features, pricingForUi } = detail;
// allow plugins to overwrite the description and features from the product details
const modalDescription = description || detailDescription;
const modalFeatures = features || detailFeatures;

const {
fullPricePerMonth: price,
currencyCode,
discountPricePerMonth: discountPrice,
introductoryOffer,
productTerm,
} = pricingForUi;
// Get pricing for a plugin - TODO: extract price to a hook or a component
const priceSource = slug === 'boost' ? pricingForUi?.tiers?.upgraded : pricingForUi;
let price, discountPrice;

if ( slug === 'boost' ) {
// component price structure
price = priceSource?.fullPrice / 12;
discountPrice = priceSource?.discountPrice / 12;
} else {
price = priceSource?.fullPricePerMonth;
discountPrice = priceSource?.discountPricePerMonth;
}

const { currencyCode, introductoryOffer, productTerm } = priceSource || {};

let priceDescription;
if ( introductoryOffer?.intervalUnit === 'month' && introductoryOffer?.intervalCount === 1 ) {
Expand All @@ -76,7 +103,9 @@ const ProductInterstitialPlugin: FC< ProductInterstitialPluginProps > = ( {

// TODO: check referrer url from product-details-card

const priceComponent = (
const priceComponent = isLoading ? (
<LoadingBlock width="100%" height="100px" />
) : (
<ProductPrice
currency={ currencyCode }
price={ price }
Expand Down Expand Up @@ -136,20 +165,30 @@ const ProductInterstitialPlugin: FC< ProductInterstitialPluginProps > = ( {
return (
<ProductInterstitialModal
title={ title }
description={ longDescription }
description={ modalDescription }
priceComponent={ priceComponent }
modalMainButton={ <ProductInterstitialModalCta slug={ slug } /> }
onOpen={ handleOpen }
onClose={ handleClose }
{ ...props }
>
<>
{ features && <ProductInterstitialFeatureList features={ features } /> }
{ modalFeatures && <ProductInterstitialFeatureList features={ modalFeatures } /> }
{ additionalContent }
{ children }
</>
</ProductInterstitialModal>
);
};

export default ProductInterstitialPlugin;
const ProductInterstitialPluginWithQueryClient: FC< ProductInterstitialPluginProps > = props => {
const queryClient = new QueryClient();

return (
<QueryClientProvider client={ queryClient }>
<ProductInterstitialPlugin { ...props } />
</QueryClientProvider>
);
};

export default ProductInterstitialPluginWithQueryClient;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

My Jetpack: add QueryProvider to ProductInterstitialModal for extendability
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import React from 'react';

interface Props {
fallback: React.ReactNode;
fallback: React.ReactElement< { error?: Error } >;
children: React.ReactNode;
}

interface State {
hasError: boolean;
error: Error | null;
error?: Error;
}

class ErrorBoundary extends React.Component< Props, State > {
constructor( props: Props ) {
super( props );
this.state = { hasError: false, error: null };
this.state = { error: undefined };
}

static getDerivedStateFromError( error: Error ): State {
return { hasError: true, error };
return { error };
}

componentDidCatch( error: Error, errorInfo: React.ErrorInfo ): void {
Expand All @@ -26,7 +25,11 @@ class ErrorBoundary extends React.Component< Props, State > {
}

render(): React.ReactNode {
if ( this.state.hasError ) {
if ( this.state.error ) {
// If fallback is a React element, pass the error as a prop
if ( React.isValidElement( this.props.fallback ) ) {
return React.cloneElement( this.props.fallback, { error: this.state.error } );
}
return this.props.fallback || null;
}
return this.props.children;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@
.content {
width: 100%;
}

.module-error-notice {
margin-bottom: 2em;

.module-error-title {
margin-bottom: 1em;
}
}
37 changes: 24 additions & 13 deletions projects/plugins/boost/app/assets/src/js/features/module/module.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { ToggleControl } from '@automattic/jetpack-components';
import { Notice, ToggleControl } from '@automattic/jetpack-components';
import { useEffect } from 'react';
import { useSingleModuleState } from './lib/stores';
import styles from './module.module.scss';
import ErrorBoundary from '$features/error-boundary/error-boundary';
import { __ } from '@wordpress/i18n';
import { __, sprintf } from '@wordpress/i18n';
import { isWoaHosting } from '$lib/utils/hosting';
import { useNotices } from '$features/notice/context';

Expand Down Expand Up @@ -106,20 +106,31 @@ const Module = ( {
);
};

const ModuleErrorFallback = ( { error, ...props }: { error: Error } & ModuleProps ) => {
return (
<div className={ styles[ 'module-error-notice' ] }>
<h3 className={ styles[ 'module-error-title' ] }>{ props.title }</h3>

<Notice
level="error"
title={ __( 'Failed to load module', 'jetpack-boost' ) }
hideCloseButton={ true }
>
<p>
{
// translators: %s is error message
sprintf( __( 'Error: %s', 'jetpack-boost' ), error.message )
}
</p>
</Notice>
</div>
);
};

export default ( props: ModuleProps ) => {
return (
<ErrorBoundary
fallback={
<div>
<div className={ styles.content }>
<h3>{ props.title }</h3>

<div className={ styles.description }>
{ __( `Failed to load module.`, 'jetpack-boost' ) }
</div>
</div>
</div>
}
fallback={ <ModuleErrorFallback error={ new Error( 'Unknown error' ) } { ...props } /> }
>
<Module { ...props } />
</ErrorBoundary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ProductInterstitialMyJetpack } from '@automattic/jetpack-my-jetpack/components/product-interstitial-modal';
import boostImage from '@automattic/jetpack-my-jetpack/components/product-interstitial/boost.png';
import { __ } from '@wordpress/i18n';
import UpgradeCTA from '$features/upgrade-cta/upgrade-cta';

type InterstitialModalCTAProps = {
description: string;
identifier: string;
};

const InterstitialModalCTA = ( { description, identifier }: InterstitialModalCTAProps ) => {
return (
<ProductInterstitialMyJetpack
slug="boost"
customModalTrigger={ <UpgradeCTA identifier={ identifier } description={ description } /> }
buttonLabel={ __( 'Upgrade now', 'jetpack-boost' ) }
isWithVideo={ false }
secondaryColumn={
<div>
<img src={ boostImage } alt="Boost" />
</div>
}
secondaryButtonHref="https://jetpack.com/boost/"
description={ __(
'Unlock the full potential of Jetpack Boost with automated performance optimization tools and more.',
'jetpack-boost'
) }
features={ [
__( 'Automated Critical CSS Generation', 'jetpack-boost' ),
__( 'Automated Image Scanning', 'jetpack-boost' ),
__( 'In-depth Performance Insights', 'jetpack-boost' ),
__( 'Customizable Image Optimization', 'jetpack-boost' ),
__( 'Expert Support With Personal Assistance Available', 'jetpack-boost' ),
] }
/>
);
};

export default InterstitialModalCTA;
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ type UpgradeCTAProps = {
description: string;
identifier: string;
eventName?: string;
onClick?: () => void;
};

const UpgradeCTA = ( {
description,
identifier,
onClick,
eventName = 'upsell_cta_from_settings_page_in_plugin',
}: UpgradeCTAProps ) => {
// No need to show the upgrade CTA if the site is unreachable.
Expand All @@ -24,8 +26,14 @@ const UpgradeCTA = ( {

const navigate = useNavigate();

const showBenefits = () => {
const onClickHandler = () => {
recordBoostEvent( eventName, { identifier } );

if ( onClick ) {
onClick();
return;
}

navigate( '/upgrade' );
};

Expand All @@ -38,7 +46,7 @@ const UpgradeCTA = ( {
: '_';

return (
<button className={ styles[ 'upgrade-cta' ] } onClick={ showBenefits }>
<button className={ styles[ 'upgrade-cta' ] } onClick={ onClickHandler }>
<div className={ styles.body }>
<p>{ description }</p>
<p className={ styles[ 'action-line' ] }>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import SpeculationMethod from '$features/speculation-rules/speculation-method';
import Pill from '$features/ui/pill/pill';
import Upgraded from '$features/ui/upgraded/upgraded';
import UpgradeCTA from '$features/upgrade-cta/upgrade-cta';
import InterstitialModalCTA from '$features/upgrade-cta/interstitial-modal-cta';
import { usePremiumFeatures } from '$lib/stores/premium-features';
import { recordBoostEvent } from '$lib/utils/analytics';
import { Notice, getRedirectUrl } from '@automattic/jetpack-components';
import { createInterpolateElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import React from 'react';
import styles from './index.module.scss';

const Index = () => {
Expand Down Expand Up @@ -93,7 +93,7 @@ const Index = () => {
>
<CriticalCssMeta />

<UpgradeCTA
<InterstitialModalCTA
identifier="critical-css"
description={ __(
'Save time by upgrading to Automatic Critical CSS generation.',
Expand Down
Loading
Loading