From 53c4fe6afb9a01e2e1f5488d04ebf067a574892c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?berislav=20grgi=C4=8Dak?= Date: Fri, 27 Oct 2023 06:08:27 +0200 Subject: [PATCH] My subscription product updates (#40752) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Marketplace: Populate the table rows with components * Marketplace: add links to dropdown menu and fix fallback product icons * Marketplace: fix popover width * Add My subscriptions install (#40630) * Marketplace: add plugin install from the subscriptions page Co-authored-by: berislav grgičak * Marketplace: Use the activation function to show install button --------- Co-authored-by: raicem * Add WP updates script to the extensions page * Add update button * Add update data to subscriptions * Update plugins * Prevent update if license unavailable * Add changefile(s) from automation for the following project(s): woocommerce * Remove all data from API * Linter fixes * Linter fixes * Remove merge string * Update link style * Add comment for updates.js * Prevent updates if required data is missing * Return removed slug code * My subscriptions action modals (#40934) * Add renew modal * Update install to run until new data loaded * Add activate modal * Add connect modal * Add renew button * Renewal button * Rename activate to connect * Add subscribe button * Add action buttons * Remove unused const * Add changefile(s) from automation for the following project(s): woocommerce * Update plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/my-subscriptions.scss Co-authored-by: And Finally * Update import path --------- Co-authored-by: And Finally Co-authored-by: github-actions --------- Co-authored-by: raicem Co-authored-by: github-actions Co-authored-by: And Finally --- .../marketplace/components/constants.ts | 1 + .../my-subscriptions/my-subscriptions.scss | 26 +++ .../table/actions/connect-button.tsx | 81 +++++++++ .../table/actions/connect-modal.tsx | 54 ++++++ .../table/{rows => actions}/install.tsx | 40 ++--- .../table/actions/renew-button.tsx | 31 ++++ .../table/actions/renew-modal.tsx | 53 ++++++ .../table/actions/subscribe-button.tsx | 28 +++ .../table/actions/subscribe-modal.tsx | 53 ++++++ .../my-subscriptions/table/actions/update.tsx | 165 ++++++++++++++++++ .../table/rows/activation-toggle.tsx | 25 --- .../my-subscriptions/table/rows/functions.tsx | 53 ++++-- .../my-subscriptions/table/rows/version.tsx | 11 ++ .../my-subscriptions/table/table-rows.tsx | 13 +- .../my-subscriptions/table/table.tsx | 21 +-- .../components/my-subscriptions/types.ts | 4 + .../contexts/subscriptions-context.tsx | 10 +- .../client/marketplace/contexts/types.ts | 2 +- .../client/marketplace/utils/functions.tsx | 43 +++-- .../client/typings/global.d.ts | 9 + .../add-my-subscription-product-update | 4 + .../add-my-subscriptions-action-modals | 4 + .../includes/admin/helper/class-wc-helper.php | 8 + .../src/Internal/Admin/Marketplace.php | 18 ++ 24 files changed, 656 insertions(+), 101 deletions(-) create mode 100644 plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/connect-button.tsx create mode 100644 plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/connect-modal.tsx rename plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/{rows => actions}/install.tsx (78%) create mode 100644 plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/renew-button.tsx create mode 100644 plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/renew-modal.tsx create mode 100644 plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/subscribe-button.tsx create mode 100644 plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/subscribe-modal.tsx create mode 100644 plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/update.tsx delete mode 100644 plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/activation-toggle.tsx create mode 100644 plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/version.tsx create mode 100644 plugins/woocommerce/changelog/add-my-subscription-product-update create mode 100644 plugins/woocommerce/changelog/add-my-subscriptions-action-modals diff --git a/plugins/woocommerce-admin/client/marketplace/components/constants.ts b/plugins/woocommerce-admin/client/marketplace/components/constants.ts index ec068ebf1bd..2ec0658822f 100644 --- a/plugins/woocommerce-admin/client/marketplace/components/constants.ts +++ b/plugins/woocommerce-admin/client/marketplace/components/constants.ts @@ -7,3 +7,4 @@ export const MARKETPLACE_CATEGORY_API_PATH = '/wp-json/wccom-extensions/1.0/categories'; export const MARKETPLACE_ITEMS_PER_PAGE = 60; export const MARKETPLACE_SEARCH_RESULTS_PER_PAGE = 8; +export const MARKETPLACE_CART_PATH = MARKETPLACE_HOST + '/cart/'; diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/my-subscriptions.scss b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/my-subscriptions.scss index 66343baf9b8..32d6b569588 100644 --- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/my-subscriptions.scss +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/my-subscriptions.scss @@ -102,6 +102,19 @@ margin-bottom: 0; } +.woocommerce-marketplace__my-subscriptions-version { + padding: 6px 12px; +} +.woocommerce-marketplace__my-subscriptions__table__header--version > span { + display: inline-block; + padding: 0 12px; +} + +.woocommerce-marketplace__my-subscriptions__table .components-button.is-link { + text-decoration: none; + padding: 6px 12px; +} + .woocommerce-marketplace__my-subscriptions--connect { display: flex; flex-direction: column; @@ -131,3 +144,16 @@ margin-bottom: $grid-unit-30; } } + +.woocommerce-marketplace__my-subscriptions__table__header--actions { + text-align: right; +} + +.woocommerce-marketplace__my-subscriptions__actions { + display: flex; + justify-content: end; + + .components-button { + margin-right: $grid-unit-10; + } +} diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/connect-button.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/connect-button.tsx new file mode 100644 index 00000000000..0a469a20903 --- /dev/null +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/connect-button.tsx @@ -0,0 +1,81 @@ +/** + * External dependencies + */ +import { Button, Icon } from '@wordpress/components'; +import { useDispatch } from '@wordpress/data'; +import { useContext, useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { SubscriptionsContext } from '~/marketplace/contexts/subscriptions-context'; +import { connectProduct } from '~/marketplace/utils/functions'; +import { Subscription } from '../../types'; + +interface ConnectProps { + subscription: Subscription; + onClose?: () => void; + variant?: Button.ButtonVariant; +} + +export default function ConnectButton( props: ConnectProps ) { + const [ isConnecting, setIsConnecting ] = useState( false ); + const { createWarningNotice, createSuccessNotice } = + useDispatch( 'core/notices' ); + const { loadSubscriptions } = useContext( SubscriptionsContext ); + + const connect = () => { + setIsConnecting( true ); + connectProduct( props.subscription.product_key ) + .then( () => { + loadSubscriptions( false ).then( () => { + createSuccessNotice( + sprintf( + // translators: %s is the product name. + __( '%s successfully connected.', 'woocommerce' ), + props.subscription.product_name + ), + { + icon: , + } + ); + setIsConnecting( false ); + if ( props.onClose ) { + props.onClose(); + } + } ); + } ) + .catch( () => { + createWarningNotice( + sprintf( + // translators: %s is the product name. + __( '%s couldn’t be connected.', 'woocommerce' ), + props.subscription.product_name + ), + { + actions: [ + { + label: __( 'Try again', 'woocommerce' ), + onClick: connect, + }, + ], + } + ); + setIsConnecting( false ); + if ( props.onClose ) { + props.onClose(); + } + } ); + }; + return ( + + ); +} diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/connect-modal.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/connect-modal.tsx new file mode 100644 index 00000000000..5bfcd22168b --- /dev/null +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/connect-modal.tsx @@ -0,0 +1,54 @@ +/** + * External dependencies + */ +import { Button, ButtonGroup, Modal } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { Subscription } from '../../types'; +import ConnectButton from './connect-button'; + +interface ConnectProps { + subscription: Subscription; + onClose: () => void; +} + +export default function ConnectModal( props: ConnectProps ) { + return ( + +

+ { sprintf( + // translators: %s is the product version number (e.g. 1.0.2). + __( + 'Version %s is available. To enable this update you need to connect your subscription to this store.', + 'woocommerce' + ), + props.subscription.version + ) } +

+ + + + +
+ ); +} diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/install.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/install.tsx similarity index 78% rename from plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/install.tsx rename to plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/install.tsx index c4c88bfcd87..2b0a88d6967 100644 --- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/install.tsx +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/install.tsx @@ -1,23 +1,23 @@ /** * External dependencies */ -import { __, sprintf } from '@wordpress/i18n'; import { Button, Icon } from '@wordpress/components'; -import { useContext, useState } from '@wordpress/element'; import { useDispatch } from '@wordpress/data'; +import { useContext, useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ -import { Subscription } from '../../types'; -import { installProduct } from '../../../../utils/functions'; import { SubscriptionsContext } from '../../../../contexts/subscriptions-context'; +import { installProduct } from '../../../../utils/functions'; +import { Subscription } from '../../types'; -interface ActivationToggleProps { +interface InstallProps { subscription: Subscription; } -export default function Install( props: ActivationToggleProps ) { +export default function Install( props: InstallProps ) { const [ loading, setLoading ] = useState( false ); const { createWarningNotice, createSuccessNotice } = useDispatch( 'core/notices' ); @@ -27,17 +27,19 @@ export default function Install( props: ActivationToggleProps ) { setLoading( true ); installProduct( props.subscription.product_key ) .then( () => { - loadSubscriptions( false ); - createSuccessNotice( - sprintf( - // translators: %s is the product name. - __( '%s successfully installed.', 'woocommerce' ), - props.subscription.product_name - ), - { - icon: , - } - ); + loadSubscriptions( false ).then( () => { + createSuccessNotice( + sprintf( + // translators: %s is the product name. + __( '%s successfully installed.', 'woocommerce' ), + props.subscription.product_name + ), + { + icon: , + } + ); + setLoading( false ); + } ); } ) .catch( () => { createWarningNotice( @@ -55,8 +57,6 @@ export default function Install( props: ActivationToggleProps ) { ], } ); - } ) - .finally( () => { setLoading( false ); } ); }; @@ -71,7 +71,7 @@ export default function Install( props: ActivationToggleProps ) { return ( + ); +} diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/renew-modal.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/renew-modal.tsx new file mode 100644 index 00000000000..7417f854b0f --- /dev/null +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/renew-modal.tsx @@ -0,0 +1,53 @@ +/** + * External dependencies + */ +import { Button, ButtonGroup, Modal } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { Subscription } from '../../types'; +import RenewButton from './renew-button'; + +interface RenewProps { + subscription: Subscription; + onClose: () => void; +} + +export default function RenewModal( props: RenewProps ) { + return ( + +

+ { sprintf( + // translators: %s is the product version number (e.g. 1.0.2). + __( + 'Version %s is available. To enable this update you need to renew your subscription.', + 'woocommerce' + ), + props.subscription.version + ) } +

+ + + + +
+ ); +} diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/subscribe-button.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/subscribe-button.tsx new file mode 100644 index 00000000000..8da349931e2 --- /dev/null +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/subscribe-button.tsx @@ -0,0 +1,28 @@ +/** + * External dependencies + */ +import { Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { MARKETPLACE_CART_PATH } from '../../../constants'; +import { appendURLParams } from '../../../../utils/functions'; +import { Subscription } from '../../types'; + +interface SubscribeProps { + subscription: Subscription; + variant?: Button.ButtonVariant; +} + +export default function SubscribeButton( props: SubscribeProps ) { + const subscribeUrl = appendURLParams( MARKETPLACE_CART_PATH, [ + [ 'add-to-cart', props.subscription.product_id.toString() ], + ] ); + return ( + + ); +} diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/subscribe-modal.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/subscribe-modal.tsx new file mode 100644 index 00000000000..1a8fc5f8e97 --- /dev/null +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/subscribe-modal.tsx @@ -0,0 +1,53 @@ +/** + * External dependencies + */ +import { Button, ButtonGroup, Modal } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { Subscription } from '../../types'; +import SubscribeButton from './subscribe-button'; + +interface SubscribeProps { + subscription: Subscription; + onClose: () => void; +} + +export default function SubscribeModal( props: SubscribeProps ) { + return ( + +

+ { sprintf( + // translators: %s is the product version number (e.g. 1.0.2). + __( + 'Version %s is available. To enable this update you need to purchase a subscription.', + 'woocommerce' + ), + props.subscription.version + ) } +

+ + + + +
+ ); +} diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/update.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/update.tsx new file mode 100644 index 00000000000..e5463a316bf --- /dev/null +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/update.tsx @@ -0,0 +1,165 @@ +/** + * External dependencies + */ +import { Button, Icon } from '@wordpress/components'; +import { useDispatch } from '@wordpress/data'; +import { useContext, useState } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { SubscriptionsContext } from '../../../../contexts/subscriptions-context'; +import { Subscription } from '../../types'; +import ConnectModal from './connect-modal'; +import RenewModal from './renew-modal'; +import SubscribeModal from './subscribe-modal'; + +interface UpdateProps { + subscription: Subscription; +} + +export default function Update( props: UpdateProps ) { + const [ showModal, setShowModal ] = useState( false ); + const [ isUpdating, setIsUpdating ] = useState( false ); + const { createWarningNotice, createSuccessNotice } = + useDispatch( 'core/notices' ); + const { loadSubscriptions } = useContext( SubscriptionsContext ); + + const canUpdate = + props.subscription.active && + props.subscription.local && + props.subscription.local.slug && + props.subscription.local.path; + + function update() { + if ( ! canUpdate ) { + setShowModal( true ); + return; + } + if ( ! window.wp.updates ) { + createWarningNotice( + sprintf( + // translators: %s is the product name. + __( '%s couldn’t be updated.', 'woocommerce' ), + props.subscription.product_name + ), + { + actions: [ + { + label: __( + 'Reload page and try again', + 'woocommerce' + ), + onClick: () => { + window.location.reload(); + }, + }, + ], + } + ); + return; + } + + setIsUpdating( true ); + + const action = + props.subscription.local.type === 'plugin' + ? 'update-plugin' + : 'update-theme'; + window.wp.updates + .ajax( action, { + slug: props.subscription.local.slug, + plugin: props.subscription.local.path, + theme: props.subscription.local.path, + } ) + .then( () => { + loadSubscriptions( false ); + createSuccessNotice( + sprintf( + // translators: %s is the product name. + __( '%s updated successfully.', 'woocommerce' ), + props.subscription.product_name + ), + { + icon: , + } + ); + } ) + .catch( () => { + createWarningNotice( + sprintf( + // translators: %s is the product name. + __( '%s couldn’t be updated.', 'woocommerce' ), + props.subscription.product_name + ), + { + actions: [ + { + label: __( 'Try again', 'woocommerce' ), + onClick: update, + }, + ], + } + ); + } ) + .always( () => { + setIsUpdating( false ); + } ); + } + + const modal = () => { + if ( ! showModal ) { + return null; + } + + if ( props.subscription.product_key === '' ) { + return ( + setShowModal( false ) } + subscription={ props.subscription } + /> + ); + } else if ( props.subscription.expired ) { + return ( + setShowModal( false ) } + /> + ); + } else if ( ! props.subscription.active ) { + return ( + setShowModal( false ) } + /> + ); + } + + return null; + }; + + return ( + <> + { modal() } + + + ); +} diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/activation-toggle.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/activation-toggle.tsx deleted file mode 100644 index 39da18c2eb6..00000000000 --- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/activation-toggle.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/** - * External dependencies - */ -import { ToggleControl } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import { Subscription } from '../../types'; -import Install from './install'; - -interface ActivationToggleProps { - subscription: Subscription; -} - -export default function ActivationToggle( props: ActivationToggleProps ) { - if ( - props.subscription.local.installed === false && - props.subscription.maxed === false - ) { - return ; - } - - return ; -} diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/functions.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/functions.tsx index 175e89b1ecd..549106a3c3f 100644 --- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/functions.tsx +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/functions.tsx @@ -1,18 +1,23 @@ /** * External dependencies */ -import { __, sprintf } from '@wordpress/i18n'; import { TableRow } from '@woocommerce/components/build-types/table/types'; -import { Icon, plugins } from '@wordpress/icons'; import { gmdateI18n } from '@wordpress/date'; +import { __, sprintf } from '@wordpress/i18n'; +import { Icon, plugins } from '@wordpress/icons'; /** * Internal dependencies */ import { Subscription } from '../../types'; -import StatusPopover from './status-popover'; -import ActivationToggle from './activation-toggle'; +import ConnectButton from '../actions/connect-button'; +import Install from '../actions/install'; +import RenewButton from '../actions/renew-button'; +import SubscribeButton from '../actions/subscribe-button'; +import Update from '../actions/update'; import ActionsDropdownMenu from './actions-dropdown-menu'; +import StatusPopover from './status-popover'; +import Version from './version'; // TODO: Add explanations. function getStatus( subscription: Subscription ): { @@ -56,21 +61,21 @@ function getStatus( subscription: Subscription ): { }; } -function getVersion( subscription: Subscription ): string { +function getVersion( subscription: Subscription ): string | JSX.Element { if ( subscription.local.version === subscription.version ) { - return subscription.local.version; + return ; } if ( subscription.local.version && subscription.version ) { - return subscription.local.version + ' > ' + subscription.version; + return ; } if ( subscription.version ) { - return subscription.version; + return ; } if ( subscription.local.version ) { - return subscription.local.version; + return ; } return ''; @@ -175,16 +180,26 @@ export function version( subscription: Subscription ): TableRow { }; } -export function activation( subscription: Subscription ): TableRow { - const displayElement = ; - +export function actions( subscription: Subscription ): TableRow { + let actionButton = null; + if ( subscription.product_key === '' ) { + actionButton = ; + } else if ( subscription.expired ) { + actionButton = ; + } else if ( subscription.local.installed === false ) { + actionButton = ; + } else if ( subscription.active === false ) { + actionButton = ( + + ); + } return { - display: displayElement, - }; -} - -export function actions(): TableRow { - return { - display: , + display: ( +
+ { actionButton } + + +
+ ), }; } diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/version.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/version.tsx new file mode 100644 index 00000000000..e42db888f70 --- /dev/null +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/version.tsx @@ -0,0 +1,11 @@ +interface VersionProps { + span: string; +} + +export default function Version( props: VersionProps ) { + return ( + + { props.span } + + ); +} diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/table-rows.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/table-rows.tsx index bdb8e7d257b..4d60d1669c4 100644 --- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/table-rows.tsx +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/table-rows.tsx @@ -7,13 +7,12 @@ import { TableRow } from '@woocommerce/components/build-types/table/types'; */ import { Subscription } from '../types'; import { + actions, + autoRenew, + expiry, productName, status, - expiry, - autoRenew, version, - activation, - actions, } from './rows/functions'; export function availableSubscriptionRow( item: Subscription ): TableRow[] { @@ -23,8 +22,7 @@ export function availableSubscriptionRow( item: Subscription ): TableRow[] { expiry( item ), autoRenew( item ), version( item ), - activation( item ), - actions(), + actions( item ), ]; } @@ -35,7 +33,6 @@ export function installedSubscriptionRow( item: Subscription ): TableRow[] { expiry( item ), autoRenew( item ), version( item ), - activation( item ), - actions(), + actions( item ), ]; } diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/table.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/table.tsx index 0eaea521baa..0052b423102 100644 --- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/table.tsx +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/table.tsx @@ -1,12 +1,12 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; import { Table, TablePlaceholder } from '@woocommerce/components'; import { TableHeader, TableRow, } from '@woocommerce/components/build-types/table/types'; +import { __ } from '@wordpress/i18n'; const tableHeadersDefault = [ { @@ -45,10 +45,19 @@ function SubscriptionsTable( props: { ); } + const headersWithClasses = props.headers.map( ( header ) => { + return { + ...header, + cellClassName: + 'woocommerce-marketplace__my-subscriptions__table__header--' + + header.key, + }; + } ); + return ( ); @@ -60,10 +69,6 @@ export function InstalledSubscriptionsTable( props: { } ) { const headers = [ ...tableHeadersDefault, - { - key: 'activated', - label: __( 'Activated', 'woocommerce' ), - }, { key: 'actions', label: __( 'Actions', 'woocommerce' ), @@ -85,10 +90,6 @@ export function AvailableSubscriptionsTable( props: { } ) { const headers = [ ...tableHeadersDefault, - { - key: 'install', - label: __( 'Install', 'woocommerce' ), - }, { key: 'actions', label: __( 'Actions', 'woocommerce' ), diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/types.ts b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/types.ts index c9f5cdd585b..d5121b9d84c 100644 --- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/types.ts +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/types.ts @@ -4,6 +4,7 @@ export type Subscription = { product_name: string; product_url: string; product_icon: string; + zip_slug: string; key_type: string; key_type_label: string; autorenew: boolean; @@ -30,6 +31,9 @@ export interface SubscriptionLocal { installed: boolean; active: boolean; version: string; + type: string; + slug: string; + path: string; } export interface SubscriptionShare { diff --git a/plugins/woocommerce-admin/client/marketplace/contexts/subscriptions-context.tsx b/plugins/woocommerce-admin/client/marketplace/contexts/subscriptions-context.tsx index 0438045048a..e3efabfcfe6 100644 --- a/plugins/woocommerce-admin/client/marketplace/contexts/subscriptions-context.tsx +++ b/plugins/woocommerce-admin/client/marketplace/contexts/subscriptions-context.tsx @@ -13,7 +13,7 @@ import { fetchSubscriptions } from '../utils/functions'; export const SubscriptionsContext = createContext< SubscriptionsContextType >( { subscriptions: [], setSubscriptions: () => {}, - loadSubscriptions: () => {}, + loadSubscriptions: () => new Promise( () => {} ), isLoading: true, setIsLoading: () => {}, } ); @@ -31,7 +31,7 @@ export function SubscriptionsContextProvider( props: { setIsLoading( true ); } - fetchSubscriptions() + return fetchSubscriptions() .then( ( subscriptionResponse ) => { setSubscriptions( subscriptionResponse ); } ) @@ -42,12 +42,14 @@ export function SubscriptionsContextProvider( props: { } ); }; - useEffect( () => loadSubscriptions( true ), [] ); + useEffect( () => { + loadSubscriptions( true ); + }, [] ); const contextValue = { subscriptions, setSubscriptions, - loadSubscriptions, + loadSubscriptions: () => loadSubscriptions(), isLoading, setIsLoading, }; diff --git a/plugins/woocommerce-admin/client/marketplace/contexts/types.ts b/plugins/woocommerce-admin/client/marketplace/contexts/types.ts index 274ae12b5a9..eb857e4930c 100644 --- a/plugins/woocommerce-admin/client/marketplace/contexts/types.ts +++ b/plugins/woocommerce-admin/client/marketplace/contexts/types.ts @@ -13,7 +13,7 @@ export type MarketplaceContextType = { export type SubscriptionsContextType = { subscriptions: Subscription[]; setSubscriptions: ( subscriptions: Subscription[] ) => void; - loadSubscriptions: ( toggleLoading?: boolean ) => void; + loadSubscriptions: ( toggleLoading?: boolean ) => Promise< void >; isLoading: boolean; setIsLoading: ( isLoading: boolean ) => void; }; diff --git a/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx b/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx index ad2391df67b..fb32629ed47 100644 --- a/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx +++ b/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx @@ -6,20 +6,20 @@ import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ +import { LOCALE } from '../../utils/admin-settings'; +import { CategoryAPIItem } from '../components/category-selector/types'; +import { + MARKETPLACE_CATEGORY_API_PATH, + MARKETPLACE_HOST, + MARKETPLACE_SEARCH_API_PATH, +} from '../components/constants'; +import { Subscription } from '../components/my-subscriptions/types'; import { Product, ProductType, - SearchAPIProductType, SearchAPIJSONType, + SearchAPIProductType, } from '../components/product-list/types'; -import { - MARKETPLACE_HOST, - MARKETPLACE_CATEGORY_API_PATH, - MARKETPLACE_SEARCH_API_PATH, -} from '../components/constants'; -import { CategoryAPIItem } from '../components/category-selector/types'; -import { LOCALE } from '../../utils/admin-settings'; -import { Subscription } from '../components/my-subscriptions/types'; interface ProductGroup { id: string; @@ -199,6 +199,20 @@ function installProduct( productKey: string ): Promise< void > { } ); } +function connectProduct( productKey: string ): Promise< void > { + const url = '/wc/v3/marketplace/subscriptions/activate'; + const data = new URLSearchParams(); + data.append( 'product_key', productKey ); + return apiFetch( { + path: url.toString(), + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: data, + } ); +} + // Append UTM parameters to a URL, being aware of existing query parameters const appendURLParams = ( url: string, @@ -219,11 +233,12 @@ const appendURLParams = ( }; export { - fetchSearchResults, - fetchDiscoverPageData, - fetchCategories, - fetchSubscriptions, - installProduct, ProductGroup, appendURLParams, + connectProduct, + fetchCategories, + fetchDiscoverPageData, + fetchSearchResults, + fetchSubscriptions, + installProduct, }; diff --git a/plugins/woocommerce-admin/client/typings/global.d.ts b/plugins/woocommerce-admin/client/typings/global.d.ts index a05f50e03a0..b2d3065d7e5 100644 --- a/plugins/woocommerce-admin/client/typings/global.d.ts +++ b/plugins/woocommerce-admin/client/typings/global.d.ts @@ -36,6 +36,15 @@ declare global { 'shipping-setting-tour': boolean; }; wp: { + updates?: { + ajax: ( action, data: { + slug: string; + plugin?: string; + theme?: string; + success?: function; + error?: function; + } ) => JQuery.Promise; + }; autosave?: { server: { postChanged: () => boolean; diff --git a/plugins/woocommerce/changelog/add-my-subscription-product-update b/plugins/woocommerce/changelog/add-my-subscription-product-update new file mode 100644 index 00000000000..48153f1c49e --- /dev/null +++ b/plugins/woocommerce/changelog/add-my-subscription-product-update @@ -0,0 +1,4 @@ +Significance: patch +Type: add + +Add update support to the My subscriptions page. \ No newline at end of file diff --git a/plugins/woocommerce/changelog/add-my-subscriptions-action-modals b/plugins/woocommerce/changelog/add-my-subscriptions-action-modals new file mode 100644 index 00000000000..800515f8686 --- /dev/null +++ b/plugins/woocommerce/changelog/add-my-subscriptions-action-modals @@ -0,0 +1,4 @@ +Significance: patch +Type: add +Comment: Add my subscription action buttons and modals. + diff --git a/plugins/woocommerce/includes/admin/helper/class-wc-helper.php b/plugins/woocommerce/includes/admin/helper/class-wc-helper.php index a947e58fc47..f7d02e6314d 100644 --- a/plugins/woocommerce/includes/admin/helper/class-wc-helper.php +++ b/plugins/woocommerce/includes/admin/helper/class-wc-helper.php @@ -1522,6 +1522,9 @@ class WC_Helper { 'installed' => false, 'active' => false, 'version' => null, + 'type' => null, + 'slug' => null, + 'path' => null, ); $updates = WC_Helper_Updater::get_update_data(); @@ -1538,14 +1541,19 @@ class WC_Helper { $subscription['local']['installed'] = true; $subscription['local']['version'] = $local['Version']; + $subscription['local']['type'] = $local['_type']; + $subscription['local']['path'] = $local['_filename']; + $subscription['local']['slug'] = null; if ( 'plugin' === $local['_type'] ) { + $subscription['local']['slug'] = $local['slug']; if ( is_plugin_active( $local['_filename'] ) ) { $subscription['local']['active'] = true; } elseif ( is_multisite() && is_plugin_active_for_network( $local['_filename'] ) ) { $subscription['local']['active'] = true; } } elseif ( 'theme' === $local['_type'] ) { + $subscription['local']['slug'] = $local['_stylesheet']; if ( in_array( $local['_stylesheet'], array( get_stylesheet(), get_template() ), true ) ) { $subscription['local']['active'] = true; } diff --git a/plugins/woocommerce/src/Internal/Admin/Marketplace.php b/plugins/woocommerce/src/Internal/Admin/Marketplace.php index 599a40f2dfd..172ef3dbb8f 100644 --- a/plugins/woocommerce/src/Internal/Admin/Marketplace.php +++ b/plugins/woocommerce/src/Internal/Admin/Marketplace.php @@ -18,6 +18,7 @@ class Marketplace { final public function init() { if ( FeaturesUtil::feature_is_enabled( 'marketplace' ) ) { add_action( 'admin_menu', array( $this, 'register_pages' ), 70 ); + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); } } @@ -53,4 +54,21 @@ class Marketplace { */ return apply_filters( 'woocommerce_marketplace_menu_items', $marketplace_pages ); } + + /** + * Enqueue update script. + * + * @param string $hook_suffix The current admin page. + */ + public function enqueue_scripts( $hook_suffix ) { + if ( 'woocommerce_page_wc-admin' !== $hook_suffix ) { + return; + }; + if ( ! isset( $_GET['path'] ) || '/extensions' !== $_GET['path'] ) { + return; + } + + // Enqueue WordPress updates script to enable plugin and theme installs and updates. + wp_enqueue_script( 'updates' ); + } }