Skip to content

Commit

Permalink
Partner Coupon: Add a new Partner Coupon Redeem CTA (#22413)
Browse files Browse the repository at this point in the history
Add a new partner coupon redeem CTA for connected users on the "At a Glance" page when a partner coupon is detected on the site.
The CTA has a "Remind me later" option which will store the current timestamp in localStorage and hide the CTA until 3 days have passed.

PT: pdpAdu-1U-p2
  • Loading branch information
atanas-dev authored Feb 15, 2022
1 parent d793b09 commit 2fe11ce
Show file tree
Hide file tree
Showing 23 changed files with 675 additions and 107 deletions.
10 changes: 9 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions projects/js-packages/partner-coupon/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
vendor/
node_modules/
.nyc_output/
coverage/
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

Add a new CTA to redeem a partner coupon after a connection is established and a partner coupon is detected
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
/**
* External dependencies
*/
import classNames from 'classnames';
import React, { useCallback, useEffect } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { ActionButton, getRedirectUrl } from '@automattic/jetpack-components';
import { ConnectScreen } from '@automattic/jetpack-connection';
import { __, sprintf } from '@wordpress/i18n';

/**
* Import styles
* Internal dependencies
*/
import './style.scss';
import RedeemPartnerCouponPostConnection from '../redeem-partner-coupon-post-connection';
import RedeemPartnerCouponPreConnection from '../redeem-partner-coupon-pre-connection';

const PartnerCouponRedeem = props => {
const {
Expand All @@ -26,90 +23,31 @@ const PartnerCouponRedeem = props => {
analytics,
} = props;

useEffect( () => {
if ( tracksUserData && 'object' === typeof analytics ) {
analytics.tracks.recordEvent( 'jetpack_partner_coupon_redeem_view', {
coupon: partnerCoupon.coupon_code,
partner: partnerCoupon.partner.prefix,
preset: partnerCoupon.preset,
// This is expected to always be "yes" since we do not track users
// before they have connected and agreed to our ToS, but we'll leave
// it in for historical reasons if this change some day.
connected: connectionStatus.isRegistered ? 'yes' : 'no',
} );
}
}, [ analytics, connectionStatus, partnerCoupon, tracksUserData ] );

const partnerCouponHandleClick = useCallback( () => {
if ( tracksUserData && 'object' === typeof analytics ) {
analytics.tracks.recordEvent( 'jetpack_partner_coupon_redeem_click', {
coupon: partnerCoupon.coupon_code,
partner: partnerCoupon.partner.prefix,
preset: partnerCoupon.preset,
// This is expected to always be "yes" since we do not track users
// before they have connected and agreed to our ToS, but we'll leave
// it in for historical reasons if this change some day.
connected: connectionStatus.isRegistered ? 'yes' : 'no',
} );
}

window.location.href = getRedirectUrl( 'jetpack-plugin-partner-coupon-checkout', {
path: partnerCoupon.product.slug,
site: siteRawUrl,
query: `coupon=${ partnerCoupon.coupon_code }`,
} );
}, [ analytics, connectionStatus, partnerCoupon, siteRawUrl, tracksUserData ] );

const classes = classNames( 'jetpack-partner-coupon-redeem', {
'jetpack-partner-coupon-redeem--connected': !! connectionStatus.hasConnectedOwner,
} );

return (
<div className={ classes }>
<ConnectScreen
apiNonce={ apiNonce }
registrationNonce={ registrationNonce }
apiRoot={ apiRoot }
images={ [ '/images/connect-right-partner-backup.png' ] }
if ( connectionStatus.hasConnectedOwner ) {
return (
<RedeemPartnerCouponPostConnection
assetBaseUrl={ assetBaseUrl }
from={ 'jetpack-partner-coupon' }
title={ sprintf(
/* translators: %s: Jetpack partner name. */
__( 'Welcome to Jetpack %s traveler!', 'jetpack' ),
partnerCoupon.partner.name
) }
buttonLabel={ sprintf(
/* translators: %s: Name of a Jetpack product. */
__( 'Set up & redeem %s', 'jetpack' ),
partnerCoupon.product.title
) }
redirectUri={ `admin.php?page=jetpack&partnerCoupon=${ partnerCoupon.coupon_code }` }
connectionStatus={ connectionStatus }
>
<p>
{ sprintf(
/* translators: %s: Name of a Jetpack product. */
__( 'Redeem your coupon and get started with %s for free the first year!', 'jetpack' ),
partnerCoupon.product.title
) }
</p>
<ul>
{ partnerCoupon.product.features.map( ( feature, key ) => (
<li key={ key }>{ feature }</li>
) ) }
</ul>
{ connectionStatus.hasConnectedOwner && (
<ActionButton
label={ sprintf(
/* translators: %s: Name of a Jetpack product. */
__( 'Redeem %s', 'jetpack' ),
partnerCoupon.product.title
) }
onClick={ partnerCouponHandleClick }
/>
) }
</ConnectScreen>
</div>
partnerCoupon={ partnerCoupon }
siteRawUrl={ siteRawUrl }
tracksUserData={ !! tracksUserData }
analytics={ analytics }
/>
);
}

return (
<RedeemPartnerCouponPreConnection
apiNonce={ apiNonce }
registrationNonce={ registrationNonce }
apiRoot={ apiRoot }
assetBaseUrl={ assetBaseUrl }
connectionStatus={ connectionStatus }
partnerCoupon={ partnerCoupon }
siteRawUrl={ siteRawUrl }
tracksUserData={ !! tracksUserData }
analytics={ analytics }
/>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/**
* External dependencies
*/
import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { ActionButton, JetpackLogo } from '@automattic/jetpack-components';
import { __, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import { usePartnerCouponRedemption } from '../../hooks';

/**
* Import styles
*/
import './style.scss';

export const DISMISS_LS_ITEM_NAME = 'jetpackRedeemPartnerCouponDismissedAt';
export const DISMISS_LS_ITEM_MAX_AGE = 3 * 24 * 60 * 60; // 3 days

/**
* Is partner coupon redeem CTA dismissed?
*
* @returns {boolean} Is the redeem CTA dismissed?
*/
function isDismissed() {
const dismissedAt = localStorage.getItem( DISMISS_LS_ITEM_NAME );

if (
! dismissedAt ||
new Date().getTime() > parseInt( dismissedAt ) + DISMISS_LS_ITEM_MAX_AGE * 1000
) {
return false;
}

return true;
}

/**
* Dismiss partner coupon redeem CTA.
*
* @returns {void}
*/
function dismiss() {
localStorage.setItem( DISMISS_LS_ITEM_NAME, new Date().getTime() );
}

const RedeemPartnerCouponPostConnection = props => {
const {
connectionStatus,
partnerCoupon,
assetBaseUrl,
siteRawUrl,
tracksUserData,
analytics,
} = props;
const [ dismissed, setDismissed ] = useState( isDismissed() );

const onClick = usePartnerCouponRedemption(
partnerCoupon,
siteRawUrl,
connectionStatus,
tracksUserData,
analytics
);

const onRemindMeLater = useCallback( () => {
dismiss();
setDismissed( isDismissed() );
}, [ setDismissed ] );

if ( dismissed ) {
return null;
}

let logoComponent = null;

if ( partnerCoupon.partner.logo ) {
logoComponent = (
<>
<JetpackLogo />
<span>+</span>
<img
src={ `${ assetBaseUrl }${ partnerCoupon.partner.logo.src }` }
alt={ sprintf(
/* translators: %s: Name of Jetpack partner. */
__( 'Logo of %s who are offering a coupon in partnership with Jetpack', 'jetpack' ),
partnerCoupon.partner.name
) }
width={ partnerCoupon.partner.logo.width }
height={ partnerCoupon.partner.logo.height }
/>
</>
);
} else {
logoComponent = <JetpackLogo />;
}

return (
<div className="jetpack-redeem-partner-coupon-post-connection">
<div className="jetpack-redeem-partner-coupon-post-connection__layout">
<div className="jetpack-redeem-partner-coupon-post-connection__content">
<div className="jetpack-redeem-partner-coupon-post-connection__logo">
{ logoComponent }
</div>

<h2 className="jetpack-redeem-partner-coupon-post-connection__heading">
{ __( 'One free year of Jetpack Backup', 'jetpack' ) }
</h2>
</div>
<div
className="jetpack-redeem-partner-coupon-post-connection__aside"
style={ {
backgroundImage: `url(${ assetBaseUrl }/images/jetpack-aside-background.jpg)`,
} }
>
<img src={ assetBaseUrl + '/images/cloud-checkmark.svg' } alt="" />
</div>
<div className="jetpack-redeem-partner-coupon-post-connection__subcontent">
<p>
{ sprintf(
/* translators: %s: Name of a Jetpack product. */
__(
'Redeem your coupon and get started with %s for free the first year! Never worry about losing your data, ever.',
'jetpack'
),
partnerCoupon.product.title
) }
</p>

<ul>
{ partnerCoupon.product.features.map( ( feature, key ) => (
<li key={ key }>{ feature }</li>
) ) }
</ul>

<div className="jetpack-redeem-partner-coupon-post-connection__actions">
<div>
<ActionButton
label={ sprintf(
/* translators: %s: Name of a Jetpack product. */
__( 'Redeem %s', 'jetpack' ),
partnerCoupon.product.title
) }
onClick={ onClick }
/>
</div>
<div>
<button
className="jetpack-redeem-partner-coupon-post-connection__remind-me-later"
onClick={ onRemindMeLater }
>
{ __( 'Remind me later', 'jetpack' ) }
</button>
</div>
</div>
</div>
</div>
</div>
);
};

RedeemPartnerCouponPostConnection.propTypes = {
assetBaseUrl: PropTypes.string.isRequired,
connectionStatus: PropTypes.object.isRequired,
partnerCoupon: PropTypes.object.isRequired,
siteRawUrl: PropTypes.string.isRequired,
tracksUserData: PropTypes.bool.isRequired,
analytics: PropTypes.object,
};

export default RedeemPartnerCouponPostConnection;
Loading

0 comments on commit 2fe11ce

Please sign in to comment.