Merge branch 'trunk' into patch-1
This commit is contained in:
commit
0c4967fb09
|
@ -38,7 +38,8 @@ jobs:
|
|||
- uses: 'actions/checkout@v4'
|
||||
name: 'Checkout'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# If 'base_ref' is available, the 'Build Matrix' step requires non-shallow git-history to identify changed files.
|
||||
fetch-depth: ${{ ( ( github.base_ref && '0' ) || '1' ) }}
|
||||
- uses: './.github/actions/setup-woocommerce-monorepo'
|
||||
name: 'Setup Monorepo'
|
||||
with:
|
||||
|
@ -51,6 +52,8 @@ jobs:
|
|||
// Intended behaviour of the jobs generation:
|
||||
// - PRs: run CI jobs aiming PRs and filter out jobs based on the content changes
|
||||
// - Pushes: run CI jobs aiming pushes without filtering based on the content changes
|
||||
|
||||
// github.base_ref is only available for pull_request events
|
||||
let baseRef = ${{ toJson( github.base_ref ) }};
|
||||
if ( baseRef ) {
|
||||
baseRef = `--base-ref origin/${ baseRef }`;
|
||||
|
@ -74,6 +77,12 @@ jobs:
|
|||
githubEvent = trigger;
|
||||
}
|
||||
|
||||
// `pre-release` should trigger `release-checks`, but without a 'tag' ref.
|
||||
// This will run all release-checks against the branch the workflow targeted, instead of a release artifact.
|
||||
if ( trigger === 'pre-release' ) {
|
||||
githubEvent = 'release-checks';
|
||||
}
|
||||
|
||||
const child_process = require( 'node:child_process' );
|
||||
child_process.execSync( `pnpm utils ci-jobs ${ baseRef } --event ${ githubEvent }` );
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Correct label of shipping dimensions length field.
|
|
@ -223,7 +223,7 @@ export function Edit( {
|
|||
/>
|
||||
<NumberControl
|
||||
label={ createInterpolateElement(
|
||||
__( 'Width <Side />', 'woocommerce' ),
|
||||
__( 'Length <Side />', 'woocommerce' ),
|
||||
{ Side: <span>B</span> }
|
||||
) }
|
||||
error={ dimensionsLengthValidationError }
|
||||
|
|
|
@ -13,6 +13,8 @@ export const MARKETPLACE_CATEGORY_API_PATH =
|
|||
export const MARKETPLACE_ITEMS_PER_PAGE = 60;
|
||||
export const MARKETPLACE_SEARCH_RESULTS_PER_PAGE = 8;
|
||||
export const MARKETPLACE_CART_PATH = MARKETPLACE_HOST + '/cart/';
|
||||
export const MARKETPLACE_RENEW_SUBSCRIPTON_PATH =
|
||||
MARKETPLACE_HOST + '/my-account/my-subscriptions/';
|
||||
export const MARKETPLACE_COLLABORATION_PATH =
|
||||
MARKETPLACE_HOST +
|
||||
'/document/managing-woocommerce-com-subscriptions/#transfer-a-woocommerce-com-subscription';
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
.woocommerce-marketplace__feedback-modal {
|
||||
max-width: 666px;
|
||||
}
|
||||
|
||||
.woocommerce-marketplace__feedback-modal-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
|
@ -1,267 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Modal, Button, TextareaControl } from '@wordpress/components';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { useContext, useEffect, useState } from '@wordpress/element';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './feedback-modal.scss';
|
||||
import LikertScale from '../likert-scale/likert-scale';
|
||||
import { MarketplaceContext } from '../../contexts/marketplace-context';
|
||||
|
||||
export default function FeedbackModal(): JSX.Element {
|
||||
const CUSTOMER_EFFORT_SCORE_ACTION = 'marketplace_redesign_2023';
|
||||
const LOCALSTORAGE_KEY_DISMISSAL_COUNT =
|
||||
'marketplace_redesign_2023_dismissals'; // Ensure we don't ask for feedback if the
|
||||
// user's already given feedback or declined to multiple times
|
||||
const LOCALSTORAGE_KEY_LAST_REQUESTED_DATE =
|
||||
'marketplace_redesign_2023_last_shown_date'; // Ensure we don't ask for feedback more
|
||||
// than once per day
|
||||
const SUPPRESS_IF_DISMISSED_X_TIMES = 1; // If the user dismisses the snackbar this many
|
||||
// times, stop asking for feedback
|
||||
const SUPPRESS_IF_AFTER_DATE = '2024-01-01'; // If this date is reached, stop asking for
|
||||
// feedback
|
||||
const SNACKBAR_TIMEOUT = 5000; // How long we wait before asking for feedback
|
||||
|
||||
const marketplaceContextValue = useContext( MarketplaceContext );
|
||||
const { isLoading } = marketplaceContextValue;
|
||||
|
||||
// Save that we dismissed the dialog or snackbar TODAY so we don't show it again until tomorrow (if ever)
|
||||
const dismissToday = () =>
|
||||
localStorage.setItem(
|
||||
LOCALSTORAGE_KEY_LAST_REQUESTED_DATE,
|
||||
new Date().toDateString()
|
||||
);
|
||||
|
||||
// Returns the number of times that the request for feedback has been dismissed
|
||||
const dismissedTimes = () =>
|
||||
parseInt(
|
||||
localStorage.getItem( LOCALSTORAGE_KEY_DISMISSAL_COUNT ) || '0',
|
||||
10
|
||||
);
|
||||
|
||||
// Increment the number of times that the request for feedback has been dismissed
|
||||
const incrementDismissedTimes = () => {
|
||||
dismissToday();
|
||||
localStorage.setItem(
|
||||
LOCALSTORAGE_KEY_DISMISSAL_COUNT,
|
||||
`${ dismissedTimes() + 1 }`
|
||||
);
|
||||
};
|
||||
|
||||
// Dismiss forever (by incrementing the number of dismissals to a high number), e.g. when feedback is provided
|
||||
const dismissForever = () => {
|
||||
dismissToday();
|
||||
localStorage.setItem(
|
||||
LOCALSTORAGE_KEY_DISMISSAL_COUNT,
|
||||
`${ SUPPRESS_IF_DISMISSED_X_TIMES }`
|
||||
);
|
||||
};
|
||||
|
||||
// Returns true if dismissed forever (either by dismissing at least SUPPRESS_IF_DISMISSED_X_TIMES times, or by submitting feedback)
|
||||
const isDismissedForever = () =>
|
||||
dismissedTimes() >= SUPPRESS_IF_DISMISSED_X_TIMES;
|
||||
|
||||
const [ isOpen, setOpen ] = useState( false );
|
||||
const [ thoughts, setThoughts ] = useState( '' );
|
||||
const [ easyToFind, setEasyToFind ] = useState( 0 );
|
||||
const [ easyToFindValidiationFailed, setEasyToFindValidiationFailed ] =
|
||||
useState( false );
|
||||
const [ meetsMyNeeds, setMeetsMyNeeds ] = useState( 0 );
|
||||
const [ meetsMyNeedsValidiationFailed, setMeetsMyNeedsValidiationFailed ] =
|
||||
useState( false );
|
||||
const openModal = () => setOpen( true );
|
||||
const closeModal = () => {
|
||||
incrementDismissedTimes();
|
||||
setOpen( false );
|
||||
};
|
||||
const { createNotice } = useDispatch( 'core/notices' );
|
||||
|
||||
function showSnackbar() {
|
||||
createNotice(
|
||||
'success',
|
||||
__( 'How easy is it to find an extension?', 'woocommerce' ),
|
||||
{
|
||||
type: 'snackbar',
|
||||
icon: (
|
||||
<>
|
||||
<svg
|
||||
color="#fff"
|
||||
strokeWidth="1.5"
|
||||
viewBox="0 0 28.873 8.9823"
|
||||
style={ { height: '8px', marginLeft: '-7px' } }
|
||||
>
|
||||
<path
|
||||
className="l"
|
||||
d="m4.1223 1.1216 19.12-0.014142 4.3982 3.38-4.3982 3.38-19.12-0.014142a3.34 3.34 0 0 1-2.39-0.97581 3.37 3.37 0 0 1 0.00707-4.773 3.34 3.34 0 0 1 2.383-0.98288z"
|
||||
stroke="#fff"
|
||||
/>
|
||||
<line
|
||||
className="l"
|
||||
x1="6.7669"
|
||||
x2="6.7669"
|
||||
y1="7.8533"
|
||||
y2="1.1216"
|
||||
stroke="#fff"
|
||||
/>
|
||||
<path
|
||||
className="l"
|
||||
d="m23.235 1.1146 4.4053 3.3729-4.3982 3.38a6.59 6.59 0 0 1-0.89096-3.3517 6.59 6.59 0 0 1 0.88388-3.4012z"
|
||||
stroke="#fff"
|
||||
/>
|
||||
<line
|
||||
className="l"
|
||||
x1="6.7669"
|
||||
x2="22.323"
|
||||
y1="4.4875"
|
||||
y2="4.4875"
|
||||
stroke="#fff"
|
||||
/>
|
||||
</svg>
|
||||
</>
|
||||
),
|
||||
explicitDismiss: true,
|
||||
onDismiss: incrementDismissedTimes,
|
||||
actions: [
|
||||
{
|
||||
onClick: openModal,
|
||||
label: __( 'Give feedback', 'woocommerce' ),
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function maybeShowSnackbar() {
|
||||
// Don't show if we're still loading content
|
||||
if ( isLoading ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't show if the user has already given feedback or otherwise suppressed
|
||||
if ( isDismissedForever() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't show if we've already shown today or user has declined today
|
||||
const today = new Date().toDateString();
|
||||
if (
|
||||
today ===
|
||||
localStorage.getItem( LOCALSTORAGE_KEY_LAST_REQUESTED_DATE )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timer = setTimeout( showSnackbar, SNACKBAR_TIMEOUT );
|
||||
|
||||
// Without this, navigating between screens will create a series of snackbars
|
||||
dismissToday();
|
||||
|
||||
return () => {
|
||||
clearTimeout( timer );
|
||||
};
|
||||
}
|
||||
|
||||
useEffect( maybeShowSnackbar, [ isLoading ] );
|
||||
|
||||
// We don't want the "How easy was it to find an extension?" dialog to appear forever:
|
||||
const FEEDBACK_DIALOG_CAN_APPEAR =
|
||||
new Date( SUPPRESS_IF_AFTER_DATE ) > new Date();
|
||||
if ( ! FEEDBACK_DIALOG_CAN_APPEAR ) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
function easyToFindChanged( value: number ) {
|
||||
setEasyToFindValidiationFailed( false );
|
||||
setEasyToFind( value );
|
||||
}
|
||||
|
||||
function meetsMyNeedsChanged( value: number ) {
|
||||
setMeetsMyNeedsValidiationFailed( false );
|
||||
setMeetsMyNeeds( value );
|
||||
}
|
||||
|
||||
function submit() {
|
||||
// Validate:
|
||||
if ( easyToFind === 0 || meetsMyNeeds === 0 ) {
|
||||
if ( easyToFind === 0 ) setEasyToFindValidiationFailed( true );
|
||||
if ( meetsMyNeeds === 0 ) setMeetsMyNeedsValidiationFailed( true );
|
||||
return;
|
||||
}
|
||||
|
||||
// Send event to CES:
|
||||
recordEvent( 'ces_feedback', {
|
||||
action: CUSTOMER_EFFORT_SCORE_ACTION,
|
||||
score: easyToFind,
|
||||
score_second_question: meetsMyNeeds,
|
||||
score_combined: easyToFind + meetsMyNeeds,
|
||||
thoughts,
|
||||
} );
|
||||
// Close the modal:
|
||||
setOpen( false );
|
||||
// Ensure we don't ask for feedback again:
|
||||
dismissForever();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ isOpen && (
|
||||
<Modal
|
||||
title={ __(
|
||||
'How easy was it to find an extension?',
|
||||
'woocommerce'
|
||||
) }
|
||||
onRequestClose={ closeModal }
|
||||
className="woocommerce-marketplace__feedback-modal"
|
||||
>
|
||||
<p>
|
||||
{ __(
|
||||
'Your feedback will help us create a better experience for people like you! Please tell us to what extent you agree or disagree with the statements below.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</p>
|
||||
<LikertScale
|
||||
fieldName="extension_screen_easy_to_find"
|
||||
title={ __(
|
||||
'It was easy to find an extension',
|
||||
'woocommerce'
|
||||
) }
|
||||
onValueChange={ easyToFindChanged }
|
||||
validationFailed={ easyToFindValidiationFailed }
|
||||
/>
|
||||
<LikertScale
|
||||
fieldName="extension_screen_meets_my_needs"
|
||||
title={ __(
|
||||
'The Extensions screen’s functionality meets my needs',
|
||||
'woocommerce'
|
||||
) }
|
||||
onValueChange={ meetsMyNeedsChanged }
|
||||
validationFailed={ meetsMyNeedsValidiationFailed }
|
||||
/>
|
||||
<TextareaControl
|
||||
label={ __( 'Additional thoughts', 'woocommerce' ) }
|
||||
value={ thoughts }
|
||||
onChange={ ( value: string ) => setThoughts( value ) }
|
||||
/>
|
||||
<p className="woocommerce-marketplace__feedback-modal-buttons">
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={ closeModal }
|
||||
text={ __( 'Cancel', 'woocommerce' ) }
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={ submit }
|
||||
text={ __( 'Send', 'woocommerce' ) }
|
||||
/>
|
||||
</p>
|
||||
</Modal>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -162,7 +162,6 @@
|
|||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 2px $grid-unit-10;
|
||||
margin-left: $grid-unit-15;
|
||||
margin-bottom: $grid-unit-05;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
|
@ -171,21 +170,11 @@
|
|||
&--error {
|
||||
color: var(--wp-red-red-70, #8a2424);
|
||||
background: var(--wp-red-red-0, #fcf0f1);
|
||||
|
||||
& > svg {
|
||||
margin-right: 2px;
|
||||
fill: var(--wp-red-red-70, #8a2424);
|
||||
}
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: var(--wp-yellow-yellow-70, #614200);
|
||||
background: var(--wp-yellow-yellow-0, #fcf9e8);
|
||||
|
||||
& > svg {
|
||||
margin-right: 2px;
|
||||
color: var(--wp-yellow-yellow-70, #614200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Button } from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { queueRecordEvent } from '@woocommerce/tracks';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { enableAutorenewalUrl } from '../../../../utils/functions';
|
||||
import { Subscription } from '../../types';
|
||||
|
||||
interface AutoRenewProps {
|
||||
subscription: Subscription;
|
||||
variant?: Button.ButtonVariant;
|
||||
}
|
||||
|
||||
export default function AutoRenewButton( props: AutoRenewProps ) {
|
||||
function recordTracksEvent() {
|
||||
queueRecordEvent( 'marketplace_auto_renew_button_clicked', {
|
||||
order_id: props.subscription.order_id,
|
||||
product_id: props.subscription.product_id,
|
||||
} );
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
href={ enableAutorenewalUrl( props.subscription ) }
|
||||
variant={ props.variant ?? 'secondary' }
|
||||
onClick={ recordTracksEvent }
|
||||
>
|
||||
{ __( 'Renew', 'woocommerce' ) }
|
||||
</Button>
|
||||
);
|
||||
}
|
|
@ -14,6 +14,7 @@ import { StatusLevel, Subscription } from '../../types';
|
|||
import ConnectButton from '../actions/connect-button';
|
||||
import Install from '../actions/install';
|
||||
import RenewButton from '../actions/renew-button';
|
||||
import AutoRenewButton from '../actions/auto-renew-button';
|
||||
import SubscribeButton from '../actions/subscribe-button';
|
||||
import Update from '../actions/update';
|
||||
import StatusPopover from './status-popover';
|
||||
|
@ -23,6 +24,7 @@ import {
|
|||
appendURLParams,
|
||||
renewUrl,
|
||||
subscribeUrl,
|
||||
enableAutorenewalUrl,
|
||||
} from '../../../../utils/functions';
|
||||
import {
|
||||
MARKETPLACE_COLLABORATION_PATH,
|
||||
|
@ -60,7 +62,7 @@ function getStatusBadge( subscription: Subscription ): StatusBadge | false {
|
|||
),
|
||||
sharing: (
|
||||
<a
|
||||
href={ MARKETPLACE_COLLABORATION_PATH }
|
||||
href={ MARKETPLACE_SHARING_PATH }
|
||||
rel="nofollow noopener noreferrer"
|
||||
>
|
||||
sharing
|
||||
|
@ -78,16 +80,7 @@ function getStatusBadge( subscription: Subscription ): StatusBadge | false {
|
|||
),
|
||||
};
|
||||
}
|
||||
if ( subscription.local.installed && ! subscription.active ) {
|
||||
return {
|
||||
text: __( 'Not connected', 'woocommerce' ),
|
||||
level: StatusLevel.Warning,
|
||||
explanation: __(
|
||||
'To receive updates and support, please connect your subscription to this store.',
|
||||
'woocommerce'
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if ( subscription.expired ) {
|
||||
return {
|
||||
text: __( 'Expired', 'woocommerce' ),
|
||||
|
@ -107,6 +100,14 @@ function getStatusBadge( subscription: Subscription ): StatusBadge | false {
|
|||
</a>
|
||||
),
|
||||
sharing: (
|
||||
<a
|
||||
href={ MARKETPLACE_SHARING_PATH }
|
||||
rel="nofollow noopener noreferrer"
|
||||
>
|
||||
sharing
|
||||
</a>
|
||||
),
|
||||
transferring: (
|
||||
<a
|
||||
href={ MARKETPLACE_COLLABORATION_PATH }
|
||||
rel="nofollow noopener noreferrer"
|
||||
|
@ -114,6 +115,48 @@ function getStatusBadge( subscription: Subscription ): StatusBadge | false {
|
|||
sharing
|
||||
</a>
|
||||
),
|
||||
}
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if ( subscription.local.installed && ! subscription.active ) {
|
||||
return {
|
||||
text: __( 'Not connected', 'woocommerce' ),
|
||||
level: StatusLevel.Warning,
|
||||
explanation: __(
|
||||
'To receive updates and support, please connect your subscription to this store.',
|
||||
'woocommerce'
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if ( subscription.expiring && ! subscription.autorenew ) {
|
||||
return {
|
||||
text: __( 'Expires soon', 'woocommerce' ),
|
||||
level: StatusLevel.Error,
|
||||
explanation: createInterpolateElement(
|
||||
__(
|
||||
'To receive updates and support, please <renew>renew</renew> this subscription before it expires or use a subscription from another account by <sharing>sharing</sharing> or <transferring>transferring</transferring>.',
|
||||
'woocommerce'
|
||||
),
|
||||
{
|
||||
renew: (
|
||||
<a
|
||||
href={ enableAutorenewalUrl( subscription ) }
|
||||
rel="nofollow noopener noreferrer"
|
||||
>
|
||||
renew
|
||||
</a>
|
||||
),
|
||||
sharing: (
|
||||
<a
|
||||
href={ MARKETPLACE_SHARING_PATH }
|
||||
rel="nofollow noopener noreferrer"
|
||||
>
|
||||
sharing
|
||||
</a>
|
||||
),
|
||||
transferring: (
|
||||
<a
|
||||
href={ MARKETPLACE_COLLABORATION_PATH }
|
||||
|
@ -183,8 +226,6 @@ export function nameAndStatus( subscription: Subscription ): TableRow {
|
|||
);
|
||||
}
|
||||
|
||||
const statusBadge = getStatusBadge( subscription );
|
||||
|
||||
const displayElement = (
|
||||
<div className="woocommerce-marketplace__my-subscriptions__product">
|
||||
<a
|
||||
|
@ -205,13 +246,6 @@ export function nameAndStatus( subscription: Subscription ): TableRow {
|
|||
{ subscription.product_name }
|
||||
</a>
|
||||
<span className="woocommerce-marketplace__my-subscriptions__product-statuses">
|
||||
{ statusBadge && (
|
||||
<StatusPopover
|
||||
text={ statusBadge.text }
|
||||
level={ statusBadge.level }
|
||||
explanation={ statusBadge.explanation ?? '' }
|
||||
/>
|
||||
) }
|
||||
{ subscription.is_shared && (
|
||||
<StatusPopover
|
||||
text={ __( 'Shared with you', 'woocommerce' ) }
|
||||
|
@ -289,12 +323,26 @@ export function expiry( subscription: Subscription ): TableRow {
|
|||
};
|
||||
}
|
||||
|
||||
export function autoRenew( subscription: Subscription ): TableRow {
|
||||
export function subscriptionStatus( subscription: Subscription ): TableRow {
|
||||
function getStatus() {
|
||||
const statusBadge = getStatusBadge( subscription );
|
||||
if ( statusBadge ) {
|
||||
return (
|
||||
<StatusPopover
|
||||
text={ statusBadge.text }
|
||||
level={ statusBadge.level }
|
||||
explanation={ statusBadge.explanation ?? '' }
|
||||
explanationOnHover
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return subscription.autorenew
|
||||
? __( 'Active', 'woocommerce' )
|
||||
: __( 'Cancelled', 'woocommerce' );
|
||||
}
|
||||
return {
|
||||
display: subscription.autorenew
|
||||
? __( 'On', 'woocommerce' )
|
||||
: __( 'Off', 'woocommerce' ),
|
||||
value: subscription.autorenew,
|
||||
display: getStatus(),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -322,7 +370,10 @@ export function actions( subscription: Subscription ): TableRow {
|
|||
actionButton = (
|
||||
<ConnectButton subscription={ subscription } variant="link" />
|
||||
);
|
||||
} else if ( ! subscription.autorenew ) {
|
||||
actionButton = <AutoRenewButton subscription={ subscription } />;
|
||||
}
|
||||
|
||||
return {
|
||||
display: (
|
||||
<div className="woocommerce-marketplace__my-subscriptions__actions">
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { Popover } from '@wordpress/components';
|
||||
import { Icon, info } from '@wordpress/icons';
|
||||
import { useState } from '@wordpress/element';
|
||||
import { useState, useRef, useEffect } from '@wordpress/element';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -14,35 +14,78 @@ export default function StatusPopover( props: {
|
|||
text: string;
|
||||
level: StatusLevel;
|
||||
explanation: string | JSX.Element;
|
||||
explanationOnHover?: boolean;
|
||||
} ) {
|
||||
const [ isVisible, setIsVisible ] = useState( false );
|
||||
const [ isHovered, setIsHovered ] = useState( false );
|
||||
const [ isClicked, setIsClicked ] = useState( false );
|
||||
const hoverTimeoutId = useRef< null | NodeJS.Timeout >( null );
|
||||
|
||||
useEffect( () => {
|
||||
return () => {
|
||||
if ( hoverTimeoutId.current ) {
|
||||
clearTimeout( hoverTimeoutId.current );
|
||||
}
|
||||
};
|
||||
}, [] );
|
||||
|
||||
const startHover = () => {
|
||||
if ( ! props.explanationOnHover ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( hoverTimeoutId.current ) {
|
||||
clearTimeout( hoverTimeoutId.current );
|
||||
}
|
||||
|
||||
setIsHovered( true );
|
||||
};
|
||||
|
||||
const endHover = () => {
|
||||
if ( ! props.explanationOnHover ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( hoverTimeoutId.current ) {
|
||||
clearTimeout( hoverTimeoutId.current );
|
||||
}
|
||||
|
||||
// Add a small delay in case user hovers from the button to the popover.
|
||||
// In such a case we don't want to hide the popover.
|
||||
hoverTimeoutId.current = setTimeout( () => {
|
||||
setIsHovered( false );
|
||||
}, 350 );
|
||||
};
|
||||
|
||||
function shouldShowExplanation() {
|
||||
if ( props.explanation === '' ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
return isClicked || ( props.explanationOnHover && isHovered );
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={ () => {
|
||||
setIsVisible( ! isVisible );
|
||||
} }
|
||||
className={
|
||||
'woocommerce-marketplace__my-subscriptions__product-status' +
|
||||
' ' +
|
||||
onClick={ () => setIsClicked( ! isClicked ) }
|
||||
onMouseOver={ startHover }
|
||||
onFocus={ startHover }
|
||||
onMouseOut={ endHover }
|
||||
onBlur={ endHover }
|
||||
className={ clsx(
|
||||
'woocommerce-marketplace__my-subscriptions__product-status',
|
||||
`woocommerce-marketplace__my-subscriptions__product-status--${ props.level }`
|
||||
}
|
||||
) }
|
||||
>
|
||||
<Icon icon={ info } size={ 16 } />
|
||||
{ props.text }
|
||||
{ shouldShowExplanation() && (
|
||||
<Popover
|
||||
className="woocommerce-marketplace__my-subscriptions__popover"
|
||||
position="top center"
|
||||
focusOnMount={ false }
|
||||
onMouseOver={ startHover }
|
||||
onMouseOut={ endHover }
|
||||
onFocus={ startHover }
|
||||
onBlur={ endHover }
|
||||
>
|
||||
{ props.explanation }
|
||||
</Popover>
|
||||
|
|
|
@ -8,7 +8,7 @@ import { TableRow } from '@woocommerce/components/build-types/table/types';
|
|||
import { Subscription } from '../types';
|
||||
import {
|
||||
actions,
|
||||
autoRenew,
|
||||
subscriptionStatus,
|
||||
expiry,
|
||||
nameAndStatus,
|
||||
version,
|
||||
|
@ -18,7 +18,7 @@ export function availableSubscriptionRow( item: Subscription ): TableRow[] {
|
|||
return [
|
||||
nameAndStatus( item ),
|
||||
expiry( item ),
|
||||
autoRenew( item ),
|
||||
subscriptionStatus( item ),
|
||||
version( item ),
|
||||
actions( item ),
|
||||
];
|
||||
|
@ -28,7 +28,7 @@ export function installedSubscriptionRow( item: Subscription ): TableRow[] {
|
|||
return [
|
||||
nameAndStatus( item ),
|
||||
expiry( item ),
|
||||
autoRenew( item ),
|
||||
subscriptionStatus( item ),
|
||||
version( item ),
|
||||
actions( item ),
|
||||
];
|
||||
|
|
|
@ -22,11 +22,11 @@ const tableHeadersDefault = [
|
|||
},
|
||||
{
|
||||
key: 'expiry',
|
||||
label: __( 'Expiry/Renewal date', 'woocommerce' ),
|
||||
label: __( 'Expires/Renews on', 'woocommerce' ),
|
||||
},
|
||||
{
|
||||
key: 'autoRenew',
|
||||
label: __( 'Auto-renew', 'woocommerce' ),
|
||||
key: 'subscription',
|
||||
label: __( 'Subscription', 'woocommerce' ),
|
||||
},
|
||||
{
|
||||
key: 'version',
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
import Header from './components/header/header';
|
||||
import Content from './components/content/content';
|
||||
import Footer from './components/footer/footer';
|
||||
import FeedbackModal from './components/feedback-modal/feedback-modal';
|
||||
|
||||
function MarketplaceComponents() {
|
||||
const { selectedTab } = useContext( MarketplaceContext );
|
||||
|
@ -27,7 +26,6 @@ function MarketplaceComponents() {
|
|||
<div className={ classNames }>
|
||||
<Header />
|
||||
<Content />
|
||||
<FeedbackModal />
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
MARKETPLACE_CATEGORY_API_PATH,
|
||||
MARKETPLACE_HOST,
|
||||
MARKETPLACE_SEARCH_API_PATH,
|
||||
MARKETPLACE_RENEW_SUBSCRIPTON_PATH,
|
||||
} from '../components/constants';
|
||||
import { Subscription } from '../components/my-subscriptions/types';
|
||||
import {
|
||||
|
@ -428,6 +429,16 @@ const appendURLParams = (
|
|||
return urlObject.toString();
|
||||
};
|
||||
|
||||
const enableAutorenewalUrl = ( subscription: Subscription ): string => {
|
||||
if ( ! subscription.product_key ) {
|
||||
// review subscriptions on the Marketplace
|
||||
return MARKETPLACE_RENEW_SUBSCRIPTON_PATH;
|
||||
}
|
||||
return appendURLParams( MARKETPLACE_RENEW_SUBSCRIPTON_PATH, [
|
||||
[ 'key', subscription.product_key.toString() ],
|
||||
] );
|
||||
};
|
||||
|
||||
const renewUrl = ( subscription: Subscription ): string => {
|
||||
return appendURLParams( MARKETPLACE_CART_PATH, [
|
||||
[ 'renew_product', subscription.product_id.toString() ],
|
||||
|
@ -458,6 +469,7 @@ export {
|
|||
ProductGroup,
|
||||
appendURLParams,
|
||||
connectProduct,
|
||||
enableAutorenewalUrl,
|
||||
fetchCategories,
|
||||
fetchDiscoverPageData,
|
||||
fetchSearchResults,
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Renamed columns inside In-App Marketplace > My subscriptions and added action to turn auto-renewal on for a subscription
|
Loading…
Reference in New Issue