diff --git a/projects/js-packages/components/changelog/update-upsell_page b/projects/js-packages/components/changelog/update-upsell_page new file mode 100644 index 000000000000..cd9a9a01a3e5 --- /dev/null +++ b/projects/js-packages/components/changelog/update-upsell_page @@ -0,0 +1,4 @@ +Significance: major +Type: changed + +Modify components for usages in Upsell page diff --git a/projects/js-packages/components/components/icon-tooltip/index.tsx b/projects/js-packages/components/components/icon-tooltip/index.tsx index a20577224069..e2bc858a5371 100644 --- a/projects/js-packages/components/components/icon-tooltip/index.tsx +++ b/projects/js-packages/components/components/icon-tooltip/index.tsx @@ -33,10 +33,12 @@ const IconTooltip: React.FC< IconTooltipProps > = ( { animate = true, iconCode = 'info-outline', iconSize = 18, + offset = 10, title, children, } ) => { const delay = 300; + const POPOVER_HELPER_WIDTH = 124; const [ isVisible, setIsVisible ] = useState( false ); const delayedSetIsOver = useDebounce( setIsVisible, delay ); @@ -65,10 +67,13 @@ const IconTooltip: React.FC< IconTooltipProps > = ( { noArrow: false, resize: false, flip: false, - offset: 10, // The distance (in px) between the anchor and the popover. + offset, // The distance (in px) between the anchor and the popover. }; const wrapperClassNames = classNames( 'icon-tooltip-wrapper', className ); + const iconShiftBySize = { + left: -( POPOVER_HELPER_WIDTH / 2 - iconSize / 2 ) + 'px', + }; return (
@@ -80,7 +85,7 @@ const IconTooltip: React.FC< IconTooltipProps > = ( { -
+
{ isVisible && (
diff --git a/projects/js-packages/components/components/icon-tooltip/stories/index.tsx b/projects/js-packages/components/components/icon-tooltip/stories/index.tsx index 760a3dc0af14..d275aaaba255 100644 --- a/projects/js-packages/components/components/icon-tooltip/stories/index.tsx +++ b/projects/js-packages/components/components/icon-tooltip/stories/index.tsx @@ -30,6 +30,12 @@ export default { iconCode: { control: { type: 'text' }, }, + iconSize: { + control: { type: 'number' }, + }, + offset: { + control: { type: 'number' }, + }, title: { control: { type: 'text' }, }, diff --git a/projects/js-packages/components/components/icon-tooltip/style.scss b/projects/js-packages/components/components/icon-tooltip/style.scss index da1ce4827108..d4ab6a224a6a 100644 --- a/projects/js-packages/components/components/icon-tooltip/style.scss +++ b/projects/js-packages/components/components/icon-tooltip/style.scss @@ -67,10 +67,12 @@ $arrow-color: var( --jp-gray ); } .icon-tooltip-helper { + // POPOVER_HELPER_WIDTH width: 124px; height: 18px; position: absolute; top: 0; + // -( POPOVER_HELPER_WIDTH / 2 - iconSize / 2 ) + 'px' left: -53px; pointer-events: none; } @@ -89,4 +91,6 @@ $arrow-color: var( --jp-gray ); font-weight: 400; font-size: 14px; line-height: 24px; + // Recover events + pointer-events: all; } \ No newline at end of file diff --git a/projects/js-packages/components/components/icon-tooltip/types.ts b/projects/js-packages/components/components/icon-tooltip/types.ts index 105f41e1de7b..07bfdf989086 100644 --- a/projects/js-packages/components/components/icon-tooltip/types.ts +++ b/projects/js-packages/components/components/icon-tooltip/types.ts @@ -18,4 +18,5 @@ export type IconTooltipProps = { title?: string; children?: React.ReactNode; iconSize?: number; + offset?: number; }; diff --git a/projects/js-packages/components/components/pricing-table/index.tsx b/projects/js-packages/components/components/pricing-table/index.tsx index 3a03c57b7d3c..74fc583d0baa 100644 --- a/projects/js-packages/components/components/pricing-table/index.tsx +++ b/projects/js-packages/components/components/pricing-table/index.tsx @@ -78,13 +78,14 @@ export const PricingTableItem: React.FC< PricingTableItemProps > = ( { { label || defaultLabel } { showTooltip && ( - { tooltipInfo || defaultTooltipInfo } + { tooltipInfo || defaultTooltipInfo } ) }
@@ -152,9 +153,10 @@ const PricingTable: React.FC< PricingTableProps > = ( { title, items, children } iconClassName={ styles[ 'popover-icon' ] } className={ styles.popover } placement={ 'bottom-end' } - iconSize={ 22 } + iconSize={ 14 } + offset={ 4 } > - { item.tooltipInfo } + { item.tooltipInfo } ) }
diff --git a/projects/js-packages/components/components/pricing-table/styles.module.scss b/projects/js-packages/components/components/pricing-table/styles.module.scss index 96f43414e739..ff0fc922c46f 100644 --- a/projects/js-packages/components/components/pricing-table/styles.module.scss +++ b/projects/js-packages/components/components/pricing-table/styles.module.scss @@ -122,7 +122,7 @@ } .popover-icon { - fill: var( --fill, var( --jp-gray ) ); + fill: var( --jp-gray-20 ); flex-shrink: 0; } diff --git a/projects/js-packages/components/components/pricing-table/types.ts b/projects/js-packages/components/components/pricing-table/types.ts index a661d7da5fb4..02e33fe8eb8e 100644 --- a/projects/js-packages/components/components/pricing-table/types.ts +++ b/projects/js-packages/components/components/pricing-table/types.ts @@ -9,7 +9,7 @@ export type PricingTableProps = { */ items: { name: string; - tooltipInfo?: string; + tooltipInfo?: React.ReactNode; tooltipTitle?: string; }[]; @@ -57,7 +57,7 @@ export type PricingTableItemProps = { /* * If the item has more description a popover info can contain that. */ - tooltipInfo?: string; + tooltipInfo?: React.ReactNode; /** * Title for the popover, not required. diff --git a/projects/js-packages/components/components/product-price/index.tsx b/projects/js-packages/components/components/product-price/index.tsx index afd57f90feaf..84ee7cbbfec1 100644 --- a/projects/js-packages/components/components/product-price/index.tsx +++ b/projects/js-packages/components/components/product-price/index.tsx @@ -20,6 +20,7 @@ const ProductPrice: React.FC< ProductPriceProps > = ( { leyend = __( '/month, paid yearly', 'jetpack' ), isNotConvenientPrice = false, hidePriceFraction = false, + children, } ) => { if ( ( price == null && offPrice == null ) || ! currency ) { return null; @@ -46,7 +47,7 @@ const ProductPrice: React.FC< ProductPriceProps > = ( { ) } { promoLabel && { promoLabel } }
- { { leyend } } + { children ? children : { leyend } } ); }; diff --git a/projects/js-packages/components/components/product-price/style.module.scss b/projects/js-packages/components/components/product-price/style.module.scss index 99d0b916946f..58206ec21490 100644 --- a/projects/js-packages/components/components/product-price/style.module.scss +++ b/projects/js-packages/components/components/product-price/style.module.scss @@ -34,6 +34,8 @@ .leyend { color: var( --jp-gray-40 ); margin-bottom: calc( var( --spacing-base ) * 3 ); + font-size: var(--font-body-small); + line-height: 20px; &::after { content: "\200B"; // Pseudo element to keep height diff --git a/projects/js-packages/components/components/product-price/types.ts b/projects/js-packages/components/components/product-price/types.ts index cdce5c132854..fad337a98b26 100644 --- a/projects/js-packages/components/components/product-price/types.ts +++ b/projects/js-packages/components/components/product-price/types.ts @@ -38,6 +38,11 @@ export type ProductPriceProps = { * Promo label to show top right of the price. */ promoLabel?: string; + + /** + * Alternative leyend with HTML syntax + */ + children?: React.ReactNode; }; export type PriceProps = { diff --git a/projects/packages/search/changelog/update-upsell_page b/projects/packages/search/changelog/update-upsell_page new file mode 100644 index 000000000000..e6eede92f0de --- /dev/null +++ b/projects/packages/search/changelog/update-upsell_page @@ -0,0 +1,4 @@ +Significance: major +Type: changed + +Introduce PricingTable to update Upsell page diff --git a/projects/packages/search/src/dashboard/components/pages/upsell-page.jsx b/projects/packages/search/src/dashboard/components/pages/upsell-page.jsx index ebc56171d4c5..46c900e52c90 100644 --- a/projects/packages/search/src/dashboard/components/pages/upsell-page.jsx +++ b/projects/packages/search/src/dashboard/components/pages/upsell-page.jsx @@ -4,6 +4,14 @@ import { Col, PricingCard, AdminSectionHero, + ProductPrice, + PricingTable, + PricingTableColumn, + PricingTableHeader, + PricingTableItem, + IconTooltip, + Button, + ThemeProvider, } from '@automattic/jetpack-components'; import { useSelect } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; @@ -37,9 +45,8 @@ export default function UpsellPage( { isLoading = false } ) { [ isLoading ] ); - const priceBefore = useSelect( select => select( STORE_ID ).getPriceBefore() / 12, [] ); - const priceAfter = useSelect( select => select( STORE_ID ).getPriceAfter() / 12, [] ); - const priceCurrencyCode = useSelect( select => select( STORE_ID ).getPriceCurrencyCode(), [] ); + // Introduce the gate for new pricing with URL parameter `new_pricing_202208=1` + const isNewPricing = useSelect( select => select( STORE_ID ).isNewPricing202208(), [] ); const domain = useSelect( select => select( STORE_ID ).getCalypsoSlug(), [] ); const adminUrl = useSelect( select => select( STORE_ID ).getSiteAdminUrl(), [] ); @@ -51,12 +58,217 @@ export default function UpsellPage( { isLoading = false } ) { } ); }, [ domain, adminUrl, isFullyConnected ] ); + // For old pricing card + const priceBefore = useSelect( select => select( STORE_ID ).getPriceBefore() / 12, [] ); + const priceAfter = useSelect( select => select( STORE_ID ).getPriceAfter() / 12, [] ); + const priceCurrencyCode = useSelect( select => select( STORE_ID ).getPriceCurrencyCode(), [] ); const basicInfoText = __( '14 day money back guarantee.', 'jetpack-search-pkg' ); const onSaleInfoText = __( 'Special introductory pricing, all renewals are at full price. 14 day money back guarantee.', 'jetpack-search-pkg' ); + // For new pricing table + const siteDomain = useSelect( select => select( STORE_ID ).getCalypsoSlug(), [] ); + const newPricingArgs = { + title: 'The best WordPress search experience', + items: [ + { + name: 'Number of records', + tooltipInfo: ( + <> + Records are all posts, pages, custom post types and other types of content indexed by + Jetpack Search. + + ), + }, + { + name: 'Monthly requests', + tooltipInfo: ( + <>A search request is when someone visiting your site searches for something. + ), + }, + { + name: 'Unbranded search', + tooltipInfo: <>Paid customers can remove branding from the search tool., + }, + { + name: 'Priority support', + tooltipInfo: ( + <> + Paid customers get dedicated email support from our world-class Happiness Engineers to + help with any issue. +
+
+ All other questions are handled by our team as quickly as we are able to through the + WordPress support forum. + + ), + }, + { + name: 'Instant search and indexing', + tooltipInfo: ( + <> + Instant search and filtering without reloading the page. +
+
+ Real-time indexing, so your search index will update within minutes of changes to your + site. + + ), + }, + { + name: 'Powerful filtering', + tooltipInfo: ( + <> + Filtered and faceted searches by tags, categories, dates, custom taxonomies, and post + types. + + ), + }, + { + name: 'Supports 38 languages', + tooltipInfo: ( + <> + Language support for English, Spanish, French, Portuguese, Hindi, Japanese, among + others.{ ' ' } + + See all supported languanges + + + ), + }, + { + name: 'Spelling correction', + tooltipInfo: ( + <> + Quick and accurate spelling correction for when your site visitors mistype their search. + + ), + }, + ], + }; + + const oldPricingComponent = ( + + +

{ __( 'The best WordPress search experience', 'jetpack-search-pkg' ) }

+ + + + + + +
+ ); + + const newPricingComponent = ( + + + + + + + +
+ Starting price per month, billed yearly + + <> + Starting price based on the number of records for { siteDomain }. For + every additional 10k records or requests, an additional $7.50 per month will + be charged. + + +
+
+ +
+ 10k records } /> + + + + + + + +
+ + + + + + + In the free plan, you can continue using the plugin even if you have more than + 5k records for three months.{ ' ' } + + More about indexing and query limits + + + } + /> + + In the free plan, you can continue using the plugin even if you have more than + 500 requests for three consecutive months.{ ' ' } + + More about indexing and query limits + + + } + /> + + + + + + + +
+
+ +
+ ); + return ( <> { isPageLoading && } @@ -67,26 +279,7 @@ export default function UpsellPage( { isLoading = false } ) { a8cLogoHref={ AUTOMATTIC_WEBSITE } > - - -

{ __( 'The best WordPress search experience', 'jetpack-search-pkg' ) }

- - - - - - -
+ { isNewPricing ? newPricingComponent : oldPricingComponent }
diff --git a/projects/packages/search/src/dashboard/components/pages/upsell-page.scss b/projects/packages/search/src/dashboard/components/pages/upsell-page.scss index 1763fa554f45..a07763412cde 100644 --- a/projects/packages/search/src/dashboard/components/pages/upsell-page.scss +++ b/projects/packages/search/src/dashboard/components/pages/upsell-page.scss @@ -1,3 +1,5 @@ +@import 'scss/_variables.scss'; + .jp-search-dashboard-upsell-page { line-height: 1.5; width: 100%; @@ -10,3 +12,21 @@ margin-left: 0; } } + +.price-tip { + display: flex; + height: 20px; + margin-bottom: 24px; +} + +.price-tip-text { + font-weight: 400; + font-size: 14px; + line-height: 20px; + color: $studio-gray-40; + margin-right: 7px; +} + +.price-tip-icon { + fill: $studio-gray-10; +}