From 41bf95c04c50b9b9a385597076ae39226cc752fe Mon Sep 17 00:00:00 2001 From: "okmttdhr, tada" Date: Fri, 15 Dec 2023 10:53:18 +0900 Subject: [PATCH] Live Preview: Introduce the upgrade modal in Premium and WooCommerce themes (#85013) --- .../common/override-preview-button-url.js | 4 +- .../src/live-preview-modal/style.scss | 2 +- apps/wpcom-block-editor/src/wpcom/editor.scss | 2 +- .../hooks/use-override-save-button.ts | 121 ++++++++++++++++++ .../hooks/use-previewing-theme.ts | 8 +- .../wpcom/features/live-preview/index.scss | 2 + .../src/wpcom/features/live-preview/index.tsx | 8 +- .../features/live-preview/upgrade-modal.scss | 40 ++++++ .../features/live-preview/upgrade-modal.tsx | 29 +++++ .../features/live-preview/upgrade-notice.scss | 7 + .../features/live-preview/upgrade-notice.tsx | 29 +---- apps/wpcom-block-editor/webpack.config.js | 8 ++ .../theme-tier-bundled-badge.js | 4 +- client/components/theme-type-badge/index.tsx | 4 +- .../components/theme-type-badge/tooltip.tsx | 4 +- .../components/theme-upgrade-modal/index.tsx | 34 +++-- .../components/theme-upgrade-modal/style.scss | 13 +- .../design-picker-design-title.tsx | 4 +- .../theme/hooks/use-bundle-settings.tsx | 32 +++-- client/my-sites/theme/main.jsx | 6 +- .../state/themes/hooks/use-theme-details.ts | 1 + 21 files changed, 292 insertions(+), 70 deletions(-) create mode 100644 apps/wpcom-block-editor/src/wpcom/features/live-preview/hooks/use-override-save-button.ts create mode 100644 apps/wpcom-block-editor/src/wpcom/features/live-preview/index.scss create mode 100644 apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-modal.scss create mode 100644 apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-modal.tsx diff --git a/apps/editing-toolkit/editing-toolkit-plugin/common/override-preview-button-url.js b/apps/editing-toolkit/editing-toolkit-plugin/common/override-preview-button-url.js index d3c3e296b5050..953228212014d 100644 --- a/apps/editing-toolkit/editing-toolkit-plugin/common/override-preview-button-url.js +++ b/apps/editing-toolkit/editing-toolkit-plugin/common/override-preview-button-url.js @@ -53,7 +53,9 @@ async function overridePreviewButtonUrl() { } ); const popoverSlotElem = document.querySelector( '.interface-interface-skeleton ~ .popover-slot' ); - popoverSlotObserver.observe( popoverSlotElem, { childList: true } ); + if ( popoverSlotElem ) { + popoverSlotObserver.observe( popoverSlotElem, { childList: true } ); + } } overridePreviewButtonUrl(); diff --git a/apps/editing-toolkit/editing-toolkit-plugin/wpcom-block-editor-nux/src/live-preview-modal/style.scss b/apps/editing-toolkit/editing-toolkit-plugin/wpcom-block-editor-nux/src/live-preview-modal/style.scss index a35ed8688cddd..a72b56fa73caa 100644 --- a/apps/editing-toolkit/editing-toolkit-plugin/wpcom-block-editor-nux/src/live-preview-modal/style.scss +++ b/apps/editing-toolkit/editing-toolkit-plugin/wpcom-block-editor-nux/src/live-preview-modal/style.scss @@ -49,7 +49,7 @@ } .dialog__backdrop.live-preview-modal__overlay { - background-color: rgba(0, 0, 0, 0.7); + background-color: rgba(var(--color-neutral-70-rgb), 0.8); // Dialog sets `z-index: z-index("root", ".dialog__backdrop")`, // but this modal is used outside of Calypso, so we need to set it manually. // We can refactor to use Modal from `@wordpress/components` instead of Dialog. diff --git a/apps/wpcom-block-editor/src/wpcom/editor.scss b/apps/wpcom-block-editor/src/wpcom/editor.scss index bd896c391b002..d33d81f76d46e 100644 --- a/apps/wpcom-block-editor/src/wpcom/editor.scss +++ b/apps/wpcom-block-editor/src/wpcom/editor.scss @@ -1,5 +1,5 @@ @import "./features/use-classic-block-guide"; -@import "./features/live-preview/upgrade-notice"; +@import "./features/live-preview"; .blog-onboarding-hide { .components-external-link, // "Connect an account" link in Jetpack sidebar diff --git a/apps/wpcom-block-editor/src/wpcom/features/live-preview/hooks/use-override-save-button.ts b/apps/wpcom-block-editor/src/wpcom/features/live-preview/hooks/use-override-save-button.ts new file mode 100644 index 0000000000000..b02d9adc4b0e0 --- /dev/null +++ b/apps/wpcom-block-editor/src/wpcom/features/live-preview/hooks/use-override-save-button.ts @@ -0,0 +1,121 @@ +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +import { useEffect } from 'react'; +import { getUnlock } from '../utils'; + +const SAVE_HUB_SAVE_BUTTON_SELECTOR = '.edit-site-save-hub__button'; +const HEADER_SAVE_BUTTON_SELECTOR = '.edit-site-save-button__button'; + +const unlock = getUnlock(); + +/** + * This overrides the `SaveButton` behavior by adding a listener and changing the copy. + * Our objective is to open a custom modal ('ThemeUpgradeModal') instead of proceeding with the default behavior. + * For more context, see the discussion on adding an official customization method: https://github.com/WordPress/gutenberg/pull/56807. + */ +export const useOverrideSaveButton = ( { + setIsThemeUpgradeModalOpen, +}: { + setIsThemeUpgradeModalOpen: ( isThemeUpgradeModalOpen: boolean ) => void; +} ) => { + const canvasMode = useSelect( + ( select ) => + unlock && select( 'core/edit-site' ) && unlock( select( 'core/edit-site' ) ).getCanvasMode(), + [] + ); + + useEffect( () => { + const saveButtonClickHandler: EventListener = ( e ) => { + e.preventDefault(); + e.stopPropagation(); + setIsThemeUpgradeModalOpen( true ); + }; + const overrideSaveButtonClick = ( selector: string ) => { + const button = document.querySelector( selector ); + if ( button ) { + button.textContent = __( 'Upgrade now', 'wpcom-live-preview' ); + button.addEventListener( 'click', saveButtonClickHandler ); + } + }; + + /** + * This overrides the tooltip text for the save button. + * + * The tooltip is shown after a delay. + * So we observe the DOM changes to avoid being fragile to the delay. + * The observer is activated only when the user hovers over the button to optimize performance. + */ + const observer = new MutationObserver( ( mutations ) => { + mutations.forEach( ( mutation ) => { + mutation.addedNodes.forEach( ( node ) => { + if ( node.nodeType === Node.ELEMENT_NODE ) { + const tooltip = ( node as Element ).querySelector( '.components-tooltip' ); + if ( tooltip ) { + tooltip.textContent = __( 'Upgrade now', 'wpcom-live-preview' ); + } + } + } ); + } ); + } ); + const startObserver = () => { + observer.observe( document.body, { childList: true } ); + }; + const stopObserver = () => { + observer.disconnect(); + }; + const overrideSaveButtonHover = ( selector: string ) => { + const button = document.querySelector( selector ); + if ( button ) { + button.addEventListener( 'mouseover', startObserver ); + button.addEventListener( 'mouseout', stopObserver ); + } + }; + + if ( canvasMode === 'view' ) { + overrideSaveButtonClick( SAVE_HUB_SAVE_BUTTON_SELECTOR ); + overrideSaveButtonHover( SAVE_HUB_SAVE_BUTTON_SELECTOR ); + return; + } + if ( canvasMode === 'edit' ) { + overrideSaveButtonClick( HEADER_SAVE_BUTTON_SELECTOR ); + overrideSaveButtonHover( HEADER_SAVE_BUTTON_SELECTOR ); + return; + } + + return () => { + document + .querySelector( SAVE_HUB_SAVE_BUTTON_SELECTOR ) + ?.removeEventListener( 'click', saveButtonClickHandler ); + document + .querySelector( HEADER_SAVE_BUTTON_SELECTOR ) + ?.removeEventListener( 'click', saveButtonClickHandler ); + document + .querySelector( SAVE_HUB_SAVE_BUTTON_SELECTOR ) + ?.removeEventListener( 'mouseover', startObserver ); + document + .querySelector( HEADER_SAVE_BUTTON_SELECTOR ) + ?.removeEventListener( 'mouseover', startObserver ); + document + .querySelector( SAVE_HUB_SAVE_BUTTON_SELECTOR ) + ?.removeEventListener( 'mouseout', stopObserver ); + document + .querySelector( HEADER_SAVE_BUTTON_SELECTOR ) + ?.removeEventListener( 'mouseout', stopObserver ); + }; + }, [ canvasMode, setIsThemeUpgradeModalOpen ] ); + + useEffect( () => { + // This overrides the keyboard shortcut (⌘S) for saving. + const overrideSaveButtonKeyboardShortcut = ( e: KeyboardEvent ) => { + if ( e.key === 's' && ( e.metaKey || e.ctrlKey ) ) { + e.preventDefault(); + e.stopPropagation(); + setIsThemeUpgradeModalOpen( true ); + } + }; + document.addEventListener( 'keydown', overrideSaveButtonKeyboardShortcut ); + return () => { + document.removeEventListener( 'keydown', overrideSaveButtonKeyboardShortcut ); + }; + }, [ setIsThemeUpgradeModalOpen ] ); +}; diff --git a/apps/wpcom-block-editor/src/wpcom/features/live-preview/hooks/use-previewing-theme.ts b/apps/wpcom-block-editor/src/wpcom/features/live-preview/hooks/use-previewing-theme.ts index c477020a7ac03..e348a798e9478 100644 --- a/apps/wpcom-block-editor/src/wpcom/features/live-preview/hooks/use-previewing-theme.ts +++ b/apps/wpcom-block-editor/src/wpcom/features/live-preview/hooks/use-previewing-theme.ts @@ -31,15 +31,14 @@ export const usePreviewingTheme = () => { }, [] ); const [ previewingTheme, setPreviewingTheme ] = useState< Theme | undefined >( undefined ); + const previewingThemeId = + ( previewingThemeSlug as string )?.split( '/' )?.[ 1 ] || previewingThemeSlug; const previewingThemeName = previewingTheme?.name || previewingThemeSlug; const previewingThemeType = previewingTheme ? getThemeType( previewingTheme ) : undefined; const previewingThemeTypeDisplay = previewingThemeType === WOOCOMMERCE_THEME ? 'WooCommerce' : 'Premium'; useEffect( () => { - const previewingThemeId = - ( previewingThemeSlug as string )?.split( '/' )?.[ 1 ] || previewingThemeSlug; - if ( previewingThemeId ) { wpcom.req .get( `/themes/${ previewingThemeId }`, { apiVersion: '1.2' } ) @@ -53,9 +52,10 @@ export const usePreviewingTheme = () => { setPreviewingTheme( undefined ); } return; - }, [ previewingThemeSlug ] ); + }, [ previewingThemeId ] ); return { + id: previewingThemeId, name: previewingThemeName, type: previewingThemeType, typeDisplay: previewingThemeTypeDisplay, diff --git a/apps/wpcom-block-editor/src/wpcom/features/live-preview/index.scss b/apps/wpcom-block-editor/src/wpcom/features/live-preview/index.scss new file mode 100644 index 0000000000000..50849b4024428 --- /dev/null +++ b/apps/wpcom-block-editor/src/wpcom/features/live-preview/index.scss @@ -0,0 +1,2 @@ +@import "./upgrade-modal"; +@import "./upgrade-notice"; diff --git a/apps/wpcom-block-editor/src/wpcom/features/live-preview/index.tsx b/apps/wpcom-block-editor/src/wpcom/features/live-preview/index.tsx index 15fee5c9f4d26..74b420a06e86a 100644 --- a/apps/wpcom-block-editor/src/wpcom/features/live-preview/index.tsx +++ b/apps/wpcom-block-editor/src/wpcom/features/live-preview/index.tsx @@ -6,6 +6,7 @@ import { FC, useEffect } from 'react'; import { useCanPreviewButNeedUpgrade } from './hooks/use-can-preview-but-need-upgrade'; import { useHideTemplatePartHint } from './hooks/use-hide-template-part-hint'; import { usePreviewingTheme } from './hooks/use-previewing-theme'; +import { LivePreviewUpgradeModal } from './upgrade-modal'; import { LivePreviewUpgradeNotice } from './upgrade-notice'; import { getUnlock } from './utils'; @@ -77,7 +78,12 @@ const LivePreviewNoticePlugin = () => { } if ( canPreviewButNeedUpgrade ) { - return ; + return ( + <> + + + + ); } return ; }; diff --git a/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-modal.scss b/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-modal.scss new file mode 100644 index 0000000000000..ca4ff26d9eee3 --- /dev/null +++ b/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-modal.scss @@ -0,0 +1,40 @@ +@import "@automattic/typography/styles/variables"; +// stylelint-disable-next-line scss/at-import-no-partial-leading-underscore +@import "calypso/assets/stylesheets/shared/mixins/_breakpoints"; +// stylelint-disable-next-line scss/at-import-no-partial-leading-underscore +@import "calypso/assets/stylesheets/shared/_loading"; + +// Overriding `.wp-core-ui` styles back to Calypso styles. +.wpcom-live-preview-upgrade-modal { + &.card { + border: none; + } + .theme-upgrade-modal p { + font-size: $font-body; + } + .theme-upgrade-modal .theme-upgrade-modal__actions.bundle { + .button { + color: var(--color-neutral-70); + border-color: var(--color-neutral-10); + background-color: transparent; + font-size: $font-body-small; + line-height: 22px; + &.is-primary { + color: var(--color-text-inverted); + border-color: var(--color-primary); + background-color: var(--color-primary); + } + } + } + .theme-upgrade-modal__included h2 { + margin-top: 0; + line-height: 1.5; + } +} + +.dialog__backdrop.wpcom-live-preview-upgrade-modal__overlay { + background-color: rgba(var(--color-neutral-70-rgb), 0.8); + // Dialog sets `z-index: z-index("root", ".dialog__backdrop")`, + // but this modal is used outside of Calypso, so we need to set it manually. + z-index: 100000; +} diff --git a/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-modal.tsx b/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-modal.tsx new file mode 100644 index 0000000000000..e2036600cd5f0 --- /dev/null +++ b/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-modal.tsx @@ -0,0 +1,29 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { FC, useState } from 'react'; +import { ThemeUpgradeModal } from 'calypso/components/theme-upgrade-modal'; +import { useOverrideSaveButton } from './hooks/use-override-save-button'; + +import './upgrade-modal.scss'; + +export const LivePreviewUpgradeModal: FC< { themeId: string; upgradePlan: () => void } > = ( { + themeId, + upgradePlan, +} ) => { + const [ isThemeUpgradeModalOpen, setIsThemeUpgradeModalOpen ] = useState( false ); + + useOverrideSaveButton( { setIsThemeUpgradeModalOpen } ); + + const queryClient = new QueryClient(); + return ( + + setIsThemeUpgradeModalOpen( false ) } + checkout={ upgradePlan } + /> + + ); +}; diff --git a/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-notice.scss b/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-notice.scss index 3397369dbc3fd..8fe83b1bf0c49 100644 --- a/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-notice.scss +++ b/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-notice.scss @@ -6,6 +6,13 @@ .wpcom-live-preview-upgrade-notice-view-container { padding: 24px 24px 0; border-top: 1px solid #2f2f2f; + + // Hide the original border only when the notice is present. + + .edit-site-save-hub { + border-top: none; + padding-top: 0; + } + .wpcom-live-preview-upgrade-notice-view { margin: 0 0 24px; color: var(--color-text); diff --git a/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-notice.tsx b/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-notice.tsx index 235833c6537e9..5bedfa0062f42 100644 --- a/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-notice.tsx +++ b/apps/wpcom-block-editor/src/wpcom/features/live-preview/upgrade-notice.tsx @@ -21,21 +21,13 @@ const unlock = getUnlock(); const LivePreviewUpgradeNoticeView: FC< { noticeText: string; - upgradePlan: () => void; -} > = ( { noticeText, upgradePlan } ) => { +} > = ( { noticeText } ) => { return ( { noticeText } @@ -45,8 +37,7 @@ const LivePreviewUpgradeNoticeView: FC< { export const LivePreviewUpgradeNotice: FC< { dashboardLink?: string; previewingTheme: ReturnType< typeof usePreviewingTheme >; - upgradePlan: () => void; -} > = ( { dashboardLink, previewingTheme, upgradePlan } ) => { +} > = ( { dashboardLink, previewingTheme } ) => { const [ isRendered, setIsRendered ] = useState( false ); const { createWarningNotice } = useDispatch( 'core/notices' ); const canvasMode = useSelect( @@ -75,11 +66,6 @@ export const LivePreviewUpgradeNotice: FC< { isDismissible: false, __unstableHTML: true, actions: [ - { - label: __( 'Upgrade now', 'wpcom-live-preview' ), - onClick: upgradePlan, - variant: 'primary', - }, ...( dashboardLink ? [ { @@ -91,7 +77,7 @@ export const LivePreviewUpgradeNotice: FC< { : [] ), ], } ); - }, [ createWarningNotice, dashboardLink, noticeText, upgradePlan ] ); + }, [ createWarningNotice, dashboardLink, noticeText ] ); /** * Show the notice when the canvas mode is 'view'. @@ -119,13 +105,10 @@ export const LivePreviewUpgradeNotice: FC< { container.insertBefore( noticeContainer, saveHub ); } - render( - , - noticeContainer - ); + render( , noticeContainer ); setIsRendered( true ); - }, [ canvasMode, isRendered, noticeText, upgradePlan ] ); + }, [ canvasMode, isRendered, noticeText ] ); return null; }; diff --git a/apps/wpcom-block-editor/webpack.config.js b/apps/wpcom-block-editor/webpack.config.js index 7808e139f98f3..4f064fa03842c 100644 --- a/apps/wpcom-block-editor/webpack.config.js +++ b/apps/wpcom-block-editor/webpack.config.js @@ -5,6 +5,7 @@ const path = require( 'path' ); const getBaseWebpackConfig = require( '@automattic/calypso-build/webpack.config.js' ); const DependencyExtractionWebpackPlugin = require( '@wordpress/dependency-extraction-webpack-plugin' ); +const webpack = require( 'webpack' ); /** * Internal variables @@ -58,6 +59,13 @@ function getWebpackConfig( ...webpackConfig.plugins.filter( ( plugin ) => plugin.constructor.name !== 'DependencyExtractionWebpackPlugin' ), + /** + * This is needed for import-ing ThemeUpgradeModal, + * which is directly due to the use of NODE_DEBUG in the package called `util`. + */ + new webpack.DefinePlugin( { + 'process.env.NODE_DEBUG': JSON.stringify( process.env.NODE_DEBUG || false ), + } ), new DependencyExtractionWebpackPlugin( { requestToExternal( request ) { if ( request === 'tinymce/tinymce' ) { diff --git a/client/components/theme-tier/theme-tier-badge/theme-tier-bundled-badge.js b/client/components/theme-tier/theme-tier-badge/theme-tier-bundled-badge.js index 528af0b988d1d..65d0c40babdd3 100644 --- a/client/components/theme-tier/theme-tier-badge/theme-tier-bundled-badge.js +++ b/client/components/theme-tier/theme-tier-badge/theme-tier-bundled-badge.js @@ -1,7 +1,7 @@ import { BundledBadge, PremiumBadge } from '@automattic/components'; import { createInterpolateElement } from '@wordpress/element'; import { useTranslate } from 'i18n-calypso'; -import useBundleSettings from 'calypso/my-sites/theme/hooks/use-bundle-settings'; +import { useBundleSettingsByTheme } from 'calypso/my-sites/theme/hooks/use-bundle-settings'; import { useSelector } from 'calypso/state'; import { canUseTheme } from 'calypso/state/themes/selectors'; import { getSelectedSiteId } from 'calypso/state/ui/selectors'; @@ -14,7 +14,7 @@ export default function ThemeTierBundledBadge() { const translate = useTranslate(); const siteId = useSelector( getSelectedSiteId ); const { themeId } = useThemeTierBadgeContext(); - const bundleSettings = useBundleSettings( themeId ); + const bundleSettings = useBundleSettingsByTheme( themeId ); const isThemeIncluded = useSelector( ( state ) => siteId && canUseTheme( state, siteId, themeId ) ); diff --git a/client/components/theme-type-badge/index.tsx b/client/components/theme-type-badge/index.tsx index b75f913f0483c..630454374c1e7 100644 --- a/client/components/theme-type-badge/index.tsx +++ b/client/components/theme-type-badge/index.tsx @@ -10,7 +10,7 @@ import { import classNames from 'classnames'; import { useTranslate } from 'i18n-calypso'; import { useEffect } from 'react'; -import useBundleSettings from 'calypso/my-sites/theme/hooks/use-bundle-settings'; +import { useBundleSettingsByTheme } from 'calypso/my-sites/theme/hooks/use-bundle-settings'; import { useSelector } from 'calypso/state'; import { getThemeType } from 'calypso/state/themes/selectors'; import ThemeTypeBadgeTooltip from './tooltip'; @@ -34,7 +34,7 @@ const ThemeTypeBadge = ( { }: Props ) => { const translate = useTranslate(); const type = useSelector( ( state ) => getThemeType( state, themeId ) ); - const bundleSettings = useBundleSettings( themeId ); + const bundleSettings = useBundleSettingsByTheme( themeId ); useEffect( () => { if ( type === FREE_THEME && ! isLockedStyleVariation ) { diff --git a/client/components/theme-type-badge/tooltip.tsx b/client/components/theme-type-badge/tooltip.tsx index 10910b736cd15..2d6eff9daa809 100644 --- a/client/components/theme-type-badge/tooltip.tsx +++ b/client/components/theme-type-badge/tooltip.tsx @@ -9,7 +9,7 @@ import { Button as LinkButton } from '@wordpress/components'; import { createInterpolateElement } from '@wordpress/element'; import { useTranslate } from 'i18n-calypso'; import { useEffect } from 'react'; -import useBundleSettings from 'calypso/my-sites/theme/hooks/use-bundle-settings'; +import { useBundleSettingsByTheme } from 'calypso/my-sites/theme/hooks/use-bundle-settings'; import { useSelector } from 'calypso/state'; import { canUseTheme, @@ -70,7 +70,7 @@ const ThemeTypeBadgeTooltip = ( { }: Props ) => { const translate = useTranslate(); const type = useSelector( ( state ) => getThemeType( state, themeId ) ); - const bundleSettings = useBundleSettings( themeId ); + const bundleSettings = useBundleSettingsByTheme( themeId ); const isIncludedCurrentPlan = useSelector( ( state ) => siteId && canUseTheme( state, siteId, themeId ) ); diff --git a/client/components/theme-upgrade-modal/index.tsx b/client/components/theme-upgrade-modal/index.tsx index f7a974b986bcd..aaec2931de52b 100644 --- a/client/components/theme-upgrade-modal/index.tsx +++ b/client/components/theme-upgrade-modal/index.tsx @@ -22,25 +22,27 @@ import { PLAN_ECOMMERCE, PLAN_PREMIUM, } from '@automattic/calypso-products'; -import { Button, Gridicon, Dialog, ScreenReaderText } from '@automattic/components'; +import { Button, Dialog, ScreenReaderText } from '@automattic/components'; import { ProductsList } from '@automattic/data-stores'; import { usePlans } from '@automattic/data-stores/src/plans'; import { useIsEnglishLocale } from '@automattic/i18n-utils'; import { useBreakpoint } from '@automattic/viewport-react'; import { Tooltip } from '@wordpress/components'; import { useSelect } from '@wordpress/data'; +import { Icon as WpIcon, check, close } from '@wordpress/icons'; import classNames from 'classnames'; import i18n, { useTranslate } from 'i18n-calypso'; import { LoadingEllipsis } from 'calypso/components/loading-ellipsis'; import { getPlanFeaturesObject } from 'calypso/lib/plans/features-list'; -import useBundleSettings from 'calypso/my-sites/theme/hooks/use-bundle-settings'; -import { useSelector } from 'calypso/state'; +import { useBundleSettings } from 'calypso/my-sites/theme/hooks/use-bundle-settings'; import { ProductListItem } from 'calypso/state/products-list/selectors/get-products-list'; import { useThemeDetails } from 'calypso/state/themes/hooks/use-theme-details'; -import { isExternallyManagedTheme } from 'calypso/state/themes/selectors'; +import { ThemeSoftwareSet } from 'calypso/types'; import './style.scss'; interface UpgradeModalProps { + additionalClassNames?: string; + additionalOverlayClassNames?: string; /* Theme slug */ slug: string; isOpen: boolean; @@ -58,7 +60,13 @@ interface UpgradeModalContent { action: JSX.Element | null; } +/** + * - This component provides users with details about a specific theme and outlines the plan they need to upgrade to. + * - It is also used outside of Calypso, currently in `apps/wpcom-block-editor`, so refrain from incorporating Calypso state, Gridicons, or any logic that relies on Calypso dependencies. + */ export const ThemeUpgradeModal = ( { + additionalClassNames, + additionalOverlayClassNames, slug, isOpen, isMarketplaceThemeSubscriptionNeeded, @@ -73,11 +81,15 @@ export const ThemeUpgradeModal = ( { const isDesktop = useBreakpoint( '>782px' ); // Check current theme: Does it have a plugin bundled? - const theme_software_set = theme?.data?.taxonomies?.theme_software_set?.length; - const showBundleVersion = theme_software_set; - const isExternallyManaged = useSelector( ( state ) => isExternallyManagedTheme( state, slug ) ); + const themeSoftwareSet = theme?.data?.taxonomies?.theme_software_set as + | ThemeSoftwareSet[] + | undefined; + const showBundleVersion = themeSoftwareSet?.length; + const isExternallyManaged = theme?.data?.theme_type === 'managed-external'; - const bundleSettings = useBundleSettings( slug ); + // Currently, it always get the first software set. In the future, the whole applications can be enhanced to support multiple ones. + const firstThemeSoftwareSet = themeSoftwareSet?.[ 0 ]; + const bundleSettings = useBundleSettings( firstThemeSoftwareSet?.slug ); const premiumPlanProduct = useSelect( ( select ) => select( ProductsList.store ).getProductBySlug( 'value_bundle' ), @@ -408,7 +420,7 @@ export const ThemeUpgradeModal = ( {
  • - + { feature.getTitle() }
    @@ -420,6 +432,8 @@ export const ThemeUpgradeModal = ( { return ( closeModal() } @@ -438,7 +452,7 @@ export const ThemeUpgradeModal = ( {
    { features }
    diff --git a/client/components/theme-upgrade-modal/style.scss b/client/components/theme-upgrade-modal/style.scss index 7e21c877f0595..0f2bc6eb35ceb 100644 --- a/client/components/theme-upgrade-modal/style.scss +++ b/client/components/theme-upgrade-modal/style.scss @@ -1,3 +1,5 @@ +@import "@automattic/typography/styles/variables"; +@import "@automattic/typography/styles/fonts"; @import "@wordpress/base-styles/breakpoints"; @import "@wordpress/base-styles/mixins"; @@ -167,10 +169,10 @@ $design-button-primary-hover-color: var(--color-primary-60); .theme-upgrade-modal__close { color: var(--color-text-subtle); position: absolute; - top: 0; - right: 8px; + top: 12px; + right: 12px; - .gridicon { + .wpicon { fill: var(--color-text-subtle); } } @@ -238,11 +240,10 @@ $design-button-primary-hover-color: var(--color-primary-60); } } - .gridicon { + .wpicon { fill: var(--studio-green-50); flex-shrink: 0; - margin-right: 10px; - margin-top: 4px; + margin-inline-end: 4px; } .components-popover.components-tooltip .components-popover__content { diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/design-picker-design-title.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/design-picker-design-title.tsx index bc636599fd478..f108925474b2f 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/design-picker-design-title.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/design-picker-design-title.tsx @@ -1,7 +1,7 @@ import { WPCOM_FEATURES_PREMIUM_THEMES } from '@automattic/calypso-products'; import { PremiumBadge, BundledBadge } from '@automattic/components'; import { useSelect } from '@wordpress/data'; -import useBundleSettings from 'calypso/my-sites/theme/hooks/use-bundle-settings'; +import { useBundleSettingsByTheme } from 'calypso/my-sites/theme/hooks/use-bundle-settings'; import { useSite } from '../../../../hooks/use-site'; import { SITE_STORE } from '../../../../stores'; import type { SiteSelect } from '@automattic/data-stores'; @@ -30,7 +30,7 @@ const DesignPickerDesignTitle: FC< Props > = ( { designTitle, selectedDesign } ) ) ); - const bundleSettings = useBundleSettings( selectedDesign.slug ); + const bundleSettings = useBundleSettingsByTheme( selectedDesign.slug ); let badge: React.ReactNode = null; if ( selectedDesign.software_sets && selectedDesign.software_sets.length > 0 ) { diff --git a/client/my-sites/theme/hooks/use-bundle-settings.tsx b/client/my-sites/theme/hooks/use-bundle-settings.tsx index e3d83deb20ac6..be01fd9a5f22d 100644 --- a/client/my-sites/theme/hooks/use-bundle-settings.tsx +++ b/client/my-sites/theme/hooks/use-bundle-settings.tsx @@ -9,6 +9,8 @@ import { getThemeSoftwareSet } from 'calypso/state/themes/selectors'; interface BundleSettings { /** Name field is a label for the bundle name, which can be used isolated or in the middle of a sentence. Many times used as "{name} theme". */ name: string; + /** Software name field is a label for the product name, which can be used isolated or in the middle of a sentence, like: "Installing {softwareName}" */ + softwareName: string; iconComponent: FC; color: string; designPickerBadgeTooltip: string; @@ -16,6 +18,8 @@ interface BundleSettings { bundledPluginMessage: TranslateResult; } +export type BundleSettingsHookReturn = BundleSettings | null; + const WooOnPlansIcon = () => ( ( ); -/** - * Hook to get the bundle settings for a given theme. - * If the theme doesn't have a sotfware set defined, it returns `null`. - */ -const useBundleSettings = ( themeId: string ): BundleSettings | null => { - const themeSoftwareSet = useSelector( ( state ) => getThemeSoftwareSet( state, themeId ) ); +export function useBundleSettings( themeSoftware?: string ): BundleSettingsHookReturn { const translate = useTranslate(); const isEnglishLocale = useIsEnglishLocale(); const businessPlanName = getPlan( PLAN_BUSINESS )?.getTitle() || ''; const bundleSettings = useMemo( () => { - // Currently, it always get the first software set. In the future, the whole applications can be enhanced to support multiple ones. - const themeSoftware = themeSoftwareSet[ 0 ]; - switch ( themeSoftware ) { case 'woo-on-plans': return { name: 'WooCommerce', + softwareName: 'WooCommerce', iconComponent: WooOnPlansIcon, color: '#7f54b3', designPickerBadgeTooltip: translate( @@ -73,9 +70,20 @@ const useBundleSettings = ( themeId: string ): BundleSettings | null => { default: return null; } - }, [ translate, businessPlanName, themeSoftwareSet ] ); + }, [ themeSoftware, translate, isEnglishLocale, businessPlanName ] ); return bundleSettings; -}; +} -export default useBundleSettings; +/** + * Hook to get the bundle settings for a given theme. + * If the theme doesn't have a sotfware set defined, it returns `null`. + */ +export function useBundleSettingsByTheme( themeId: string ): BundleSettingsHookReturn { + const themeSoftwareSet = useSelector( ( state ) => getThemeSoftwareSet( state, themeId ) ); + // Currently, it always get the first software set. In the future, the whole applications can be enhanced to support multiple ones. + const themeSoftware = themeSoftwareSet[ 0 ]; + const bundleSettings = useBundleSettings( themeSoftware ); + + return bundleSettings; +} diff --git a/client/my-sites/theme/main.jsx b/client/my-sites/theme/main.jsx index 92ebcf54bb095..ecd835f1bbcb9 100644 --- a/client/my-sites/theme/main.jsx +++ b/client/my-sites/theme/main.jsx @@ -46,7 +46,7 @@ import PageViewTracker from 'calypso/lib/analytics/page-view-tracker'; import { decodeEntities, preventWidows } from 'calypso/lib/formatting'; import { PerformanceTrackerStop } from 'calypso/lib/performance-tracking'; import { ReviewsSummary } from 'calypso/my-sites/marketplace/components/reviews-summary'; -import useBundleSettings from 'calypso/my-sites/theme/hooks/use-bundle-settings'; +import { useBundleSettingsByTheme } from 'calypso/my-sites/theme/hooks/use-bundle-settings'; import ActivationModal from 'calypso/my-sites/themes/activation-modal'; import { localizeThemesPath } from 'calypso/my-sites/themes/helpers'; import ThanksModal from 'calypso/my-sites/themes/thanks-modal'; @@ -128,7 +128,7 @@ const BannerUpsellDescription = ( { isSiteEligibleForManagedExternalThemes, isMarketplaceThemeSubscribed, } ) => { - const bundleSettings = useBundleSettings( themeId ); + const bundleSettings = useBundleSettingsByTheme( themeId ); const isEnglishLocale = useIsEnglishLocale(); if ( isBundledSoftwareSet && ! isExternallyManagedTheme ) { @@ -189,7 +189,7 @@ const BannerUpsellTitle = ( { isSiteEligibleForManagedExternalThemes, isMarketplaceThemeSubscribed, } ) => { - const bundleSettings = useBundleSettings( themeId ); + const bundleSettings = useBundleSettingsByTheme( themeId ); const isEnglishLocale = useIsEnglishLocale(); if ( isBundledSoftwareSet && ! isExternallyManagedTheme ) { diff --git a/client/state/themes/hooks/use-theme-details.ts b/client/state/themes/hooks/use-theme-details.ts index 7b25c1a0c75c9..5a088ba4691df 100644 --- a/client/state/themes/hooks/use-theme-details.ts +++ b/client/state/themes/hooks/use-theme-details.ts @@ -11,6 +11,7 @@ type Theme = { date_updated: string; price: string; taxonomies: Record< string, [] >; + theme_type: string; }; export function useThemeDetails( slug = '' ): UseQueryResult< Theme > {