Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Partner Coupon: Add a new Partner Coupon Redeem CTA #22413

Merged
merged 18 commits into from
Feb 15, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3851bca
Partner Coupon: Implement the first iteration of the new coupon redee…
atanas-dev Jan 19, 2022
e039be0
[not verified] Partner Coupon: Fix post-connection redeem CTA design …
atanas-dev Feb 2, 2022
32a9af8
[not verified] Jetpack: Adjust coupon redeem CTA features to match de…
atanas-dev Feb 8, 2022
7fc8956
[not verified] Jetpack: Add "Remind me later" feature to the Partner …
atanas-dev Feb 8, 2022
b5e41c3
Jetpack: Fixup Partner Coupon dependency versions
atanas-dev Feb 8, 2022
38de514
Jetpack: Add partner coupon redeem CTA test instructions
atanas-dev Feb 8, 2022
87a0258
Jetpack: Improve lower-res mobile layout for the partner coupon redee…
atanas-dev Feb 9, 2022
6da3fb4
Merge branch 'master' into add/partner-coupon-redeem-post-connection-cta
atanas-dev Feb 9, 2022
5cb14d7
Merge branch 'master' into add/partner-coupon-redeem-post-connection-cta
atanas-dev Feb 9, 2022
65d0bfa
Jetpack: Fixup project versions
atanas-dev Feb 9, 2022
7ea711f
Merge branch 'master' into add/partner-coupon-redeem-post-connection-cta
atanas-dev Feb 10, 2022
0678240
Jetpack: Improve naming and imports for partner coupon redeem CTA
atanas-dev Feb 10, 2022
ff73af9
Jetpack: Fix partner coupon redeem CTA making Jetpack admin UI unreac…
atanas-dev Feb 10, 2022
4b9c512
Jetpack: Replace partner coupon redeem dismiss cookie with localStorage
atanas-dev Feb 11, 2022
3af01e4
[not verified] Merge branch 'master' into add/partner-coupon-redeem-p…
atanas-dev Feb 11, 2022
a6b218f
Jetpack: Fixup dependency version numbers
atanas-dev Feb 11, 2022
b92b201
[not verified] Merge branch 'master' into add/partner-coupon-redeem-p…
atanas-dev Feb 14, 2022
84aa584
Jetpack: Disable partner coupon redeem CTAs in Offline mode
atanas-dev Feb 14, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 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,13 @@
/**
* 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, RedeemPartnerCouponPreConnection } from '../../index';
atanas-dev marked this conversation as resolved.
Show resolved Hide resolved

const PartnerCouponRedeem = props => {
const {
Expand All @@ -26,90 +22,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,169 @@
/**
* 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';
import cookie from 'cookie';

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

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

export const DISMISS_COOKIE_NAME = 'jp-redeem-partner-coupon-dismissed';
atanas-dev marked this conversation as resolved.
Show resolved Hide resolved
export const DISMISS_MAX_COOKIE_AGE = 24 * 60 * 60; // 1 day
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels very short, doesn't it? If I dismiss a "later" banner, I would expect it to be gone for at least a few days. A week seems like minimum to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Luchadores thoughts on the timing?

Copy link
Contributor

@Luchadores Luchadores Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not show it next time the user starts a new session?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we not show it next time the user starts a new session?

That could potentially be multiple times a day which IMO is a bit too much 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the timing to 3 days for now. Determining what a session is pretty tough, I'd say, especially if we're not going to use cookies.


/**
* Is partner coupon redeem CTA dismissed?
*
* @returns {boolean} Is the redeem CTA dismissed?
*/
function isDismissed() {
const cookies = cookie.parse( document.cookie );
return !! cookies[ DISMISS_COOKIE_NAME ];
}

/**
* Dismiss partner coupon redeem CTA.
*
* @returns {void}
*/
function dismiss() {
document.cookie = cookie.serialize( DISMISS_COOKIE_NAME, true, {
atanas-dev marked this conversation as resolved.
Show resolved Hide resolved
path: window.location.pathname,
maxAge: DISMISS_MAX_COOKIE_AGE,
} );
}

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 ] );
kallehauge marked this conversation as resolved.
Show resolved Hide resolved

if ( dismissed ) {
return null;
}

let logoComponent = null;
atanas-dev marked this conversation as resolved.
Show resolved Hide resolved

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