diff --git a/plugins/woocommerce-admin/client/marketplace/assets/images/alert.svg b/plugins/woocommerce-admin/client/marketplace/assets/images/alert.svg
new file mode 100644
index 00000000000..a28cc095e32
--- /dev/null
+++ b/plugins/woocommerce-admin/client/marketplace/assets/images/alert.svg
@@ -0,0 +1,7 @@
+
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 32d6b569588..3a6884b453f 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
@@ -110,10 +110,13 @@
padding: 0 12px;
}
-.woocommerce-marketplace__my-subscriptions__table .components-button.is-link {
+.woocommerce-marketplace__my-subscriptions .components-button.is-link {
text-decoration: none;
padding: 6px 12px;
}
+.woocommerce-marketplace__my-subscriptions .components-button.is-secondary:hover:not(:disabled) {
+ color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9));
+}
.woocommerce-marketplace__my-subscriptions--connect {
display: flex;
@@ -157,3 +160,50 @@
margin-right: $grid-unit-10;
}
}
+
+.woocommerce-marketplace__my-subscriptions__notices {
+ .components-notice {
+ margin-left: 0;
+ margin-right: 0;
+ background-color: #fff;
+ box-shadow: 0 2px 6px 0 rgba($gray-100, 0.05);
+ border: 1px solid var(--gutenberg-gray-100, #f0f0f0);
+ padding-right: $grid-unit-15;
+
+ &::before {
+ content: '';
+ display: block;
+ width: 4px;
+ height: 100%;
+ background-color: var(--wp-admin-theme-color, #007cba);
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ }
+
+ &.is-error::before {
+ background-color: $alert-red;
+ }
+
+ .components-notice__content {
+ display: flex;
+ align-items: center;
+ gap: $grid-unit-15;
+ }
+ .components-notice__dismiss.has-icon {
+ width: 24px;
+ min-width: 24px;
+ height: 24px;
+ align-self: center;
+ padding: $grid-unit-05;
+ > svg {
+ fill: $gray-900;
+ }
+ }
+ }
+ .components-notice__action.components-button.is-link {
+ margin: 0;
+ padding: 0;
+ }
+}
diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/my-subscriptions.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/my-subscriptions.tsx
index 75dd4465e91..17ec6c2a756 100644
--- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/my-subscriptions.tsx
+++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/my-subscriptions.tsx
@@ -3,9 +3,9 @@
*/
import { getNewPath } from '@woocommerce/navigation';
import { Button, Tooltip } from '@wordpress/components';
+import { help } from '@wordpress/icons';
import { useContext } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
-import { help } from '@wordpress/icons';
/**
* Internal dependencies
@@ -22,6 +22,7 @@ import {
installedSubscriptionRow,
} from './table/table-rows';
import { Subscription } from './types';
+import Notices from './notices';
export default function MySubscriptions(): JSX.Element {
const { subscriptions, isLoading } = useContext( SubscriptionsContext );
@@ -78,6 +79,9 @@ export default function MySubscriptions(): JSX.Element {
return (
+
{ __( 'Installed on this store', 'woocommerce' ) }
diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/notices.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/notices.tsx
new file mode 100644
index 00000000000..e91443cfb4a
--- /dev/null
+++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/notices.tsx
@@ -0,0 +1,49 @@
+/**
+ * External dependencies
+ */
+import { Notice } from '@wordpress/components';
+import { useSelect } from '@wordpress/data';
+
+/**
+ * Internal dependencies
+ */
+import Alert from '../../assets/images/alert.svg';
+import { Notice as NoticeType } from '../../contexts/types';
+import { noticeStore } from '../../contexts/notice-store';
+import { removeNotice } from '../../utils/functions';
+
+export default function Notices() {
+ const notices: NoticeType[] = useSelect(
+ ( select ) => select( noticeStore ).notices(),
+ []
+ );
+
+ const actions = ( notice: NoticeType ) => {
+ if ( ! notice.options?.actions ) {
+ return [];
+ }
+ return notice.options?.actions.map( ( action ) => {
+ return {
+ ...action,
+ variant: 'link',
+ className: 'is-link',
+ };
+ } );
+ };
+
+ const errorNotices = [];
+ for ( const notice of notices ) {
+ errorNotices.push(
+ removeNotice( notice.productKey ) }
+ key={ notice.productKey }
+ actions={ actions( notice ) }
+ >
+
+ { notice.message }
+
+ );
+ }
+ return <>{ errorNotices }>;
+}
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
index 233b6755c89..29e6d6cd4a4 100644
--- 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
@@ -2,7 +2,6 @@
* External dependencies
*/
import { Button, Icon } from '@wordpress/components';
-import { useDispatch } from '@wordpress/data';
import { useContext, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
@@ -10,8 +9,13 @@ import { __, sprintf } from '@wordpress/i18n';
* Internal dependencies
*/
import { SubscriptionsContext } from '../../../../contexts/subscriptions-context';
-import { connectProduct } from '../../../../utils/functions';
+import {
+ addNotice,
+ connectProduct,
+ removeNotice,
+} from '../../../../utils/functions';
import { Subscription } from '../../types';
+import { NoticeStatus } from '../../../../contexts/types';
interface ConnectProps {
subscription: Subscription;
@@ -21,21 +25,22 @@ interface ConnectProps {
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 );
+ removeNotice( props.subscription.product_key );
connectProduct( props.subscription )
.then( () => {
loadSubscriptions( false ).then( () => {
- createSuccessNotice(
+ addNotice(
+ props.subscription.product_key,
sprintf(
// translators: %s is the product name.
__( '%s successfully connected.', 'woocommerce' ),
props.subscription.product_name
),
+ NoticeStatus.Success,
{
icon: ,
}
@@ -47,12 +52,14 @@ export default function ConnectButton( props: ConnectProps ) {
} );
} )
.catch( () => {
- createWarningNotice(
+ addNotice(
+ props.subscription.product_key,
sprintf(
// translators: %s is the product name.
__( '%s couldn’t be connected.', 'woocommerce' ),
props.subscription.product_name
),
+ NoticeStatus.Error,
{
actions: [
{
diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/install.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/install.tsx
index 4431d4a2c7b..64cdb62a2fb 100644
--- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/install.tsx
+++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/actions/install.tsx
@@ -2,7 +2,7 @@
* External dependencies
*/
import { Button, Icon } from '@wordpress/components';
-import { dispatch, useDispatch, useSelect } from '@wordpress/data';
+import { dispatch, useSelect } from '@wordpress/data';
import { useContext } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
@@ -10,17 +10,20 @@ import { __, sprintf } from '@wordpress/i18n';
* Internal dependencies
*/
import { SubscriptionsContext } from '../../../../contexts/subscriptions-context';
-import { installProduct } from '../../../../utils/functions';
+import {
+ addNotice,
+ installProduct,
+ removeNotice,
+} from '../../../../utils/functions';
import { Subscription } from '../../types';
import { installingStore } from '../../../../contexts/install-store';
+import { NoticeStatus } from '../../../../contexts/types';
interface InstallProps {
subscription: Subscription;
}
export default function Install( props: InstallProps ) {
- const { createWarningNotice, createSuccessNotice } =
- useDispatch( 'core/notices' );
const { loadSubscriptions } = useContext( SubscriptionsContext );
const loading: boolean = useSelect(
@@ -45,15 +48,18 @@ export default function Install( props: InstallProps ) {
const install = () => {
startInstall();
+ removeNotice( props.subscription.product_key );
installProduct( props.subscription )
.then( () => {
loadSubscriptions( false ).then( () => {
- createSuccessNotice(
+ addNotice(
+ props.subscription.product_key,
sprintf(
// translators: %s is the product name.
__( '%s successfully installed.', 'woocommerce' ),
props.subscription.product_name
),
+ NoticeStatus.Success,
{
icon: ,
}
@@ -71,14 +77,19 @@ export default function Install( props: InstallProps ) {
if ( error?.success === false && error?.data.message ) {
errorMessage += ' ' + error.data.message;
}
- createWarningNotice( errorMessage, {
- actions: [
- {
- label: __( 'Try again', 'woocommerce' ),
- onClick: install,
- },
- ],
- } );
+ addNotice(
+ props.subscription.product_key,
+ errorMessage,
+ NoticeStatus.Error,
+ {
+ actions: [
+ {
+ label: __( 'Try again', 'woocommerce' ),
+ onClick: install,
+ },
+ ],
+ }
+ );
stopInstall();
} );
} );
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
index e5463a316bf..c0dd68e01d5 100644
--- 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
@@ -2,7 +2,6 @@
* External dependencies
*/
import { Button, Icon } from '@wordpress/components';
-import { useDispatch } from '@wordpress/data';
import { useContext, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
@@ -14,6 +13,12 @@ import { Subscription } from '../../types';
import ConnectModal from './connect-modal';
import RenewModal from './renew-modal';
import SubscribeModal from './subscribe-modal';
+import {
+ addNotice,
+ removeNotice,
+ updateProduct,
+} from '../../../../utils/functions';
+import { NoticeStatus } from '../../../../contexts/types';
interface UpdateProps {
subscription: Subscription;
@@ -22,8 +27,6 @@ interface UpdateProps {
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 =
@@ -37,13 +40,16 @@ export default function Update( props: UpdateProps ) {
setShowModal( true );
return;
}
+ removeNotice( props.subscription.product_key );
if ( ! window.wp.updates ) {
- createWarningNotice(
+ addNotice(
+ props.subscription.product_key,
sprintf(
// translators: %s is the product name.
__( '%s couldn’t be updated.', 'woocommerce' ),
props.subscription.product_name
),
+ NoticeStatus.Error,
{
actions: [
{
@@ -63,36 +69,33 @@ export default function Update( props: UpdateProps ) {
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,
- } )
+ updateProduct( props.subscription )
.then( () => {
- loadSubscriptions( false );
- createSuccessNotice(
- sprintf(
- // translators: %s is the product name.
- __( '%s updated successfully.', 'woocommerce' ),
- props.subscription.product_name
- ),
- {
- icon: ,
- }
- );
+ loadSubscriptions( false ).then( () => {
+ addNotice(
+ props.subscription.product_key,
+ sprintf(
+ // translators: %s is the product name.
+ __( '%s updated successfully.', 'woocommerce' ),
+ props.subscription.product_name
+ ),
+ NoticeStatus.Success,
+ {
+ icon: ,
+ }
+ );
+ setIsUpdating( false );
+ } );
} )
.catch( () => {
- createWarningNotice(
+ addNotice(
+ props.subscription.product_key,
sprintf(
// translators: %s is the product name.
__( '%s couldn’t be updated.', 'woocommerce' ),
props.subscription.product_name
),
+ NoticeStatus.Error,
{
actions: [
{
@@ -102,8 +105,6 @@ export default function Update( props: UpdateProps ) {
],
}
);
- } )
- .always( () => {
setIsUpdating( false );
} );
}
diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/actions-dropdown-menu.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/actions-dropdown-menu.tsx
index 2d26a6c39b5..d87e7c6c302 100644
--- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/actions-dropdown-menu.tsx
+++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/actions-dropdown-menu.tsx
@@ -15,7 +15,7 @@ export default function ActionsDropdownMenu() {
) }
controls={ [
{
- title: __( 'Manage in Woo.com', 'woocommerce' ),
+ title: __( 'Manage on Woo.com', 'woocommerce' ),
icon: external,
onClick: () => {
window.location.href =
diff --git a/plugins/woocommerce-admin/client/marketplace/contexts/install-store.tsx b/plugins/woocommerce-admin/client/marketplace/contexts/install-store.tsx
index 48659c4bbb5..46d76eaa739 100644
--- a/plugins/woocommerce-admin/client/marketplace/contexts/install-store.tsx
+++ b/plugins/woocommerce-admin/client/marketplace/contexts/install-store.tsx
@@ -3,31 +3,22 @@
*/
import { createReduxStore, register } from '@wordpress/data';
+/**
+ * Internal dependencies
+ */
+import { InstallingState } from './types';
+
const INSTALLING_STORE_NAME = 'woocommerce-admin/installing';
-interface InstallingState {
- installingProducts: string[];
+export interface InstallingStateErrorAction {
+ label: string;
+ onClick: () => void;
}
const DEFAULT_STATE: InstallingState = {
installingProducts: [],
};
-const actions = {
- startInstalling( productKey: string ) {
- return {
- type: 'START_INSTALLING',
- productKey,
- };
- },
- stopInstalling( productKey: string ) {
- return {
- type: 'STOP_INSTALLING',
- productKey,
- };
- },
-};
-
const store = createReduxStore( INSTALLING_STORE_NAME, {
reducer( state: InstallingState | undefined = DEFAULT_STATE, action ) {
switch ( action.type ) {
@@ -52,9 +43,20 @@ const store = createReduxStore( INSTALLING_STORE_NAME, {
return state;
},
-
- actions,
-
+ actions: {
+ startInstalling( productKey: string ) {
+ return {
+ type: 'START_INSTALLING',
+ productKey,
+ };
+ },
+ stopInstalling( productKey: string ) {
+ return {
+ type: 'STOP_INSTALLING',
+ productKey,
+ };
+ },
+ },
selectors: {
isInstalling(
state: InstallingState | undefined,
diff --git a/plugins/woocommerce-admin/client/marketplace/contexts/notice-store.tsx b/plugins/woocommerce-admin/client/marketplace/contexts/notice-store.tsx
new file mode 100644
index 00000000000..9616375b2dd
--- /dev/null
+++ b/plugins/woocommerce-admin/client/marketplace/contexts/notice-store.tsx
@@ -0,0 +1,90 @@
+/**
+ * External dependencies
+ */
+import { createReduxStore, register } from '@wordpress/data';
+import { Options } from '@wordpress/notices';
+
+/**
+ * Internal dependencies
+ */
+import { NoticeState, Notice, NoticeStatus } from './types';
+
+const NOTICE_STORE_NAME = 'woocommerce-admin/subscription-notices';
+
+const DEFAULT_STATE: NoticeState = {
+ notices: {},
+};
+
+const store = createReduxStore( NOTICE_STORE_NAME, {
+ reducer( state: NoticeState | undefined = DEFAULT_STATE, action ) {
+ switch ( action.type ) {
+ case 'ADD_NOTICE':
+ return {
+ ...state,
+ notices: {
+ ...state.notices,
+ [ action.productKey ]: {
+ productKey: action.productKey,
+ message: action.message,
+ status: action.status,
+ options: action.options,
+ },
+ },
+ };
+ case 'REMOVE_NOTICE':
+ const notices = { ...state.notices };
+ if ( notices[ action.productKey ] ) {
+ delete notices[ action.productKey ];
+ }
+ return {
+ ...state,
+ notices,
+ };
+ }
+
+ return state;
+ },
+ actions: {
+ addNotice(
+ productKey: string,
+ message: string,
+ status: NoticeStatus,
+ options?: Partial< Options >
+ ) {
+ return {
+ type: 'ADD_NOTICE',
+ productKey,
+ message,
+ status,
+ options,
+ };
+ },
+ removeNotice( productKey: string ) {
+ return {
+ type: 'REMOVE_NOTICE',
+ productKey,
+ };
+ },
+ },
+ selectors: {
+ notices( state: NoticeState | undefined ): Notice[] {
+ if ( ! state ) {
+ return [];
+ }
+ return Object.values( state.notices );
+ },
+ getNotice(
+ state: NoticeState | undefined,
+ productKey: string
+ ): Notice | undefined {
+ if ( ! state ) {
+ return;
+ }
+ return state.notices[ productKey ];
+ },
+ },
+} );
+
+register( store );
+
+export { store as noticeStore, NOTICE_STORE_NAME };
diff --git a/plugins/woocommerce-admin/client/marketplace/contexts/types.ts b/plugins/woocommerce-admin/client/marketplace/contexts/types.ts
index eb857e4930c..83e0d1c532d 100644
--- a/plugins/woocommerce-admin/client/marketplace/contexts/types.ts
+++ b/plugins/woocommerce-admin/client/marketplace/contexts/types.ts
@@ -1,3 +1,8 @@
+/**
+ * External dependencies
+ */
+import { Options } from '@wordpress/notices';
+
/**
* Internal dependencies
*/
@@ -17,3 +22,25 @@ export type SubscriptionsContextType = {
isLoading: boolean;
setIsLoading: ( isLoading: boolean ) => void;
};
+
+export enum NoticeStatus {
+ Success = 'success',
+ Error = 'error',
+}
+
+export interface Notice {
+ productKey: string;
+ message: string;
+ status: NoticeStatus;
+ options?: Partial< Options > | undefined;
+}
+
+export interface NoticeState {
+ notices: {
+ [ key: string ]: Notice;
+ };
+}
+
+export interface InstallingState {
+ installingProducts: string[];
+}
diff --git a/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx b/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx
index 82a6c8525f1..f0e5488b434 100644
--- a/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx
+++ b/plugins/woocommerce-admin/client/marketplace/utils/functions.tsx
@@ -3,7 +3,8 @@
*/
import apiFetch from '@wordpress/api-fetch';
import { __, sprintf } from '@wordpress/i18n';
-
+import { dispatch } from '@wordpress/data';
+import { Options } from '@wordpress/notices';
/**
* Internal dependencies
*/
@@ -21,6 +22,8 @@ import {
SearchAPIJSONType,
SearchAPIProductType,
} from '../components/product-list/types';
+import { NoticeStatus } from '../contexts/types';
+import { noticeStore } from '../contexts/notice-store';
interface ProductGroup {
id: string;
@@ -314,6 +317,35 @@ function installProduct( subscription: Subscription ): Promise< void > {
} );
}
+function updateProduct( subscription: Subscription ): Promise< void > {
+ return wpAjax( 'update-' + subscription.product_type, {
+ slug: subscription.local.slug,
+ [ subscription.product_type ]: subscription.local.path,
+ } );
+}
+
+function addNotice(
+ productKey: string,
+ message: string,
+ status?: NoticeStatus,
+ options?: Partial< Options >
+) {
+ if ( status === NoticeStatus.Error ) {
+ dispatch( noticeStore ).addNotice(
+ productKey,
+ message,
+ status,
+ options
+ );
+ } else {
+ dispatch( 'core/notices' ).createSuccessNotice( message, options );
+ }
+}
+
+const removeNotice = ( productKey: string ) => {
+ dispatch( noticeStore ).removeNotice( productKey );
+};
+
// Append UTM parameters to a URL, being aware of existing query parameters
const appendURLParams = (
url: string,
@@ -342,4 +374,7 @@ export {
fetchSearchResults,
fetchSubscriptions,
installProduct,
+ updateProduct,
+ addNotice,
+ removeNotice,
};
diff --git a/plugins/woocommerce/changelog/add-my-subscriptions-notices b/plugins/woocommerce/changelog/add-my-subscriptions-notices
new file mode 100644
index 00000000000..a6214e73e9e
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-my-subscriptions-notices
@@ -0,0 +1,4 @@
+Significance: patch
+Type: update
+Comment: Add My subscriptions error notices and move existing notices to helper functions.
+