Skip to content

Commit

Permalink
ASUB-7841 Subscriptions Paywall - migrating from v1 to v2 (#1802)
Browse files Browse the repository at this point in the history
* Subscriptions Paywall - Arc block migrating from v1 to v2

* PR feedback

* fix test

---------

Co-authored-by: Matthew Kim <[email protected]>
  • Loading branch information
LauraPinilla and mattkim93 authored Nov 20, 2023
1 parent 4fbf36a commit 945fbe3
Show file tree
Hide file tree
Showing 16 changed files with 1,693 additions and 0 deletions.
42 changes: 42 additions & 0 deletions blocks/subscriptions-block/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,45 @@
@include scss.block-components("offer");
@include scss.block-properties("offer");
}

.b-paywall {
&__overlay {
&-content {
@include scss.block-components("paywall-overlay-content");
@include scss.block-properties("paywall-overlay-content");
}

@include scss.block-components("paywall-overlay");
@include scss.block-properties("paywall-overlay");
}

&__subscription-dialog {
&-link-prompt {
@include scss.block-components("paywall-subscription-dialog-reason-prompt");
@include scss.block-properties("paywall-subscription-dialog-reason-prompt");

&-pre-link {
@include scss.block-components("paywall-subscription-dialog-link-prompt-pre-link");
@include scss.block-properties("paywall-subscription-dialog-link-prompt-pre-link");
}

&-link {
@include scss.block-components("paywall-subscription-dialog-link-prompt-link");
@include scss.block-properties("paywall-subscription-dialog-link-prompt-link");
}
}

&-offer-info{
@include scss.block-components("paywall-subscription-dialog-offer-info");
@include scss.block-properties("paywall-subscription-dialog-offer-info");
}

&-subheadline{
@include scss.block-components("paywall-subscription-dialog-subheadline");
@include scss.block-properties("paywall-subscription-dialog-subheadline");
}

@include scss.block-components("paywall-subscription-dialog");
@include scss.block-properties("paywall-subscription-dialog");
}
}
109 changes: 109 additions & 0 deletions blocks/subscriptions-block/components/PaywallOffer/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React, { useEffect, useRef, useState } from "react";
import { isServerSide } from "@wpmedia/arc-themes-components";
import isUrl from "is-url";
import useOffer from "../useOffer";
import SubscriptionOverlay from "../SubscriptionOverlay";
import SubscriptionDialog from "../SubscriptionDialog";

const isPaywallCampaignURL = (payWallCode) => payWallCode && isUrl(payWallCode);

const PaywallOffer = ({
isLoggedIn,
actionText,
actionUrl,
campaignCode = null,
displayMode,
linkPrompt,
linkText,
linkUrl,
reasonPrompt,
usePortal = true,
className
}) => {
// the paywall code (otherwise known as a campaign code)
const [payWallCode, setPayWallCode] = useState();

const { offer, fetchOffer } = useOffer({ campaignCode });

/**
* payWallOffer is the most updated offer that is returned from
* the fetchOffer method in the useOffer hook.
* We can't use the offer object that is returned from the useOffer
* directly as that causes a recursive call to the second useEffect.
*
* When the payWallOffer is updated in the second hook it's current
* value will be applied to the selectedOffer state to update the dom.
*/
const payWallOffer = useRef(offer);

const [selectedOffer, setSelectedOffer] = useState(payWallOffer.current);

/**
* If campaignCode is passed in as a prop,
* use that. Otherwise use what is returned from
* usePaywall hook and at the very least, set it
* to "default"
*/
useEffect(() => {
const campaign = campaignCode || "default";
setPayWallCode(campaign);
}, [campaignCode]);

// This will grab the offer corresponding to the paywall code
useEffect(() => {
const fetchNewOffer = async () => {
payWallOffer.current = await fetchOffer(payWallCode);
if (payWallOffer.current) {
setSelectedOffer(payWallOffer.current);
}
};
if (
payWallCode &&
!isUrl(payWallCode) &&
(!payWallOffer.current || payWallOffer.current.pw !== payWallCode)
) {
fetchNewOffer();
}
return () => {
payWallOffer.current = null;
};
}, [payWallCode, fetchOffer]);

/**
* Return null if server side, not paywalled, doesn't have an offer to show
* or if the user is logged in.
*/
if (isServerSide() || !selectedOffer) {
return null;
}

/**
* Need to determine the campaign code.
* First we see if we have been provided with a campaignCode prop.
* If not then we check if the payWallCode is not a url, if its
* not, then we use that. If it a url (Why would it be a URL??) then
* we just set the campaign code to "default"
*/
const campaign = campaignCode || (!isPaywallCampaignURL(payWallCode) ? payWallCode : "default");
const actionUrlFinal =
!campaign || campaign === "default" ? actionUrl : `${actionUrl}?campaign=${campaign}`;

return (
<SubscriptionOverlay displayMode={displayMode} usePortal={usePortal} className={className}>
<SubscriptionDialog
isLoggedIn={isLoggedIn}
actionText={actionText}
actionUrl={isPaywallCampaignURL(payWallCode) ? payWallCode : actionUrlFinal}
headline={selectedOffer.pageTitle}
linkPrompt={linkPrompt}
linkText={linkText}
linkUrl={linkUrl}
reasonPrompt={reasonPrompt}
subHeadline={selectedOffer.pageSubTitle}
className={className}
/>
</SubscriptionOverlay>
);
};

export default PaywallOffer;
122 changes: 122 additions & 0 deletions blocks/subscriptions-block/components/PaywallOffer/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import React from "react";
import { render } from "@testing-library/react";
import isUrl from "is-url";

import { isServerSide } from "@wpmedia/arc-themes-components";

import PaywallOffer from ".";
import usePaywall from "../usePaywall";
import useOffer from "../useOffer";

jest.mock("@wpmedia/arc-themes-components", () => ({
__esModule: true,
isServerSide: jest.fn(() => false),
}));

jest.mock("is-url", () => ({
__esModule: true,
default: jest.fn(() => false),
}));

jest.mock("../../components/useOffer");
jest.mock("../../../identity-block");
jest.mock("../../components/usePaywall");

useOffer.mockReturnValue({
offer: {
pageTitle: "this the offer title",
pageSubTitle: "this the offer subtitle",
},
fetchOffer: () => ({
pageTitle: "this the offer title",
pageSubTitle: "this the offer subtitle",
}),
});

usePaywall.mockReturnValue({
campaignCode: "default",
isPaywalled: true,
isRegisterwalled: false,
});

/**
* Below I pass usePortal to false that will in return
* pass this down to the Subscription Overlay.
* This boolean prevents ReactDOM.createPortal from being used.
* Just checking with isServerSide doesn't work. Jest and Enzyme
* still have poor support for ReactDOM.createPortal, so we need a way
* to conditionally render ReactDOM.createPortal.
*/
describe("The PaywallOffer component ", () => {
it("returns null if serverSide", () => {
isServerSide.mockReturnValue(true);
render(
<PaywallOffer
actionText="Subscribe"
actionUrl="/offer/"
campaignCode="defaultish"
linkPrompt="Already a subscriber?"
linkText="Log In."
linkUrl="/account/login"
reasonPrompt="Subscribe to continue reading."
usePortal={false}
/>
);
expect(screen.html()).toBe(null);
isServerSide.mockReset();
});

it("renders with correct markup", () => {
render(
<PaywallOffer
actionText="Subscribe"
actionUrl="/offer/"
campaignCode="defaultish"
linkPrompt="Already a subscriber?"
linkText="Log In."
linkUrl="/account/login"
reasonPrompt="Subscribe to continue reading."
usePortal={false}
/>
);

expect(screen.getByText("Subscribe to continue reading.")).not.toBeNull();
expect(screen.getByText("Already a subscriber?")).not.toBeNull();
expect(screen.getByText("Log In.").closest("a")).toHaveAttribute("href", "/account/login");

expect(screen.getByText("this the offer title")).not.toBeNull();
expect(screen.getByText("this the offer subtitle")).not.toBeNull();
expect(screen.getByText("Subscribe")).not.toBeNull();
expect(screen.getByText("Subscribe").closest("a")).toHaveAttribute(
"href",
"/offer/?campaign=defaultish"
);
});

it("renders campaignCode if its a url", () => {
isUrl.mockReturnValue(true);
render(
<PaywallOffer
actionText="Subscribe"
actionUrl="/offer/"
campaignCode="./"
usePortal={false}
/>
);
expect(screen.getByText("Subscribe")).not.toBeNull();
expect(screen.getByText("Subscribe").closest("a")).toHaveAttribute(
"href",
"/offer/?campaign=./"
);
isUrl.mockReset();
});

it("renders without a query param if campaignCode is not passed", () => {
isUrl.mockReturnValue(true);
render(<PaywallOffer actionText="Subscribe" actionUrl="/offer/" usePortal={false} />);
expect(screen.getByRole("button")).not.toBeNull();
expect(screen.getByText("Subscribe")).not.toBeNull();
expect(screen.getByRole("button").toHaveAttribute("href", "/offer/"));
isUrl.mockReset();
});
});
41 changes: 41 additions & 0 deletions blocks/subscriptions-block/components/RegwallOffer/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from "react";

import { isServerSide } from "@wpmedia/arc-themes-components";
import SubscriptionOverlay from "../SubscriptionOverlay";
import SubscriptionDialog from "../SubscriptionDialog";

const RegwallOffer = ({
actionText,
actionUrl,
displayMode,
headlineText,
linkPrompt,
linkText,
linkUrl,
reasonPrompt,
subheadlineText,
usePortal = true,
className,
}) => {
if (isServerSide()) {
return null;
}

return (
<SubscriptionOverlay displayMode={displayMode} usePortal={usePortal} className={className}>
<SubscriptionDialog
actionText={actionText}
actionUrl={actionUrl}
headline={headlineText}
linkPrompt={linkPrompt}
linkText={linkText}
linkUrl={linkUrl}
reasonPrompt={reasonPrompt}
subHeadline={subheadlineText}
className={className}
/>
</SubscriptionOverlay>
);
};

export default RegwallOffer;
69 changes: 69 additions & 0 deletions blocks/subscriptions-block/components/RegwallOffer/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from "react";
import { render } from "@testing-library/react";

import { isServerSide } from "@wpmedia/arc-themes-components";

import RegwallOffer from ".";

jest.mock("@wpmedia/arc-themes-components", () => ({
__esModule: true,
isServerSide: jest.fn(() => false),
}));

jest.mock("is-url", () => ({
__esModule: true,
default: jest.fn(() => false),
}));

/**
* Below I pass usePortal to false that will in return
* pass this down to the Subscription Overlay.
* This boolean prevents ReactDOM.createPortal from being used.
* Just checking with isServerSide doesn't work. Jest and Enzyme
* still have poor support for ReactDOM.createPortal, so we need a way
* to conditionally render ReactDOM.createPortal.
*/
describe("The RegwallOffer component ", () => {
it("returns null if serverSide", () => {
isServerSide.mockReturnValue(true);
render(
<RegwallOffer
actionText="Subscribe"
actionUrl="/account/signup"
headlineText="Headline"
linkPrompt="Already a subscriber?"
linkText="Log In."
linkUrl="/account/login"
reasonPrompt="Subscribe to continue reading."
subheadlineText="Subheadline"
usePortal={false}
/>
);
expect(screen.html()).toBe(null);
isServerSide.mockReset();
});

it("renders with correct markup", () => {
render(
<RegwallOffer
actionText="Subscribe"
actionUrl="/account/signup"
headlineText="Headline"
linkPrompt="Already a subscriber?"
linkText="Log In."
linkUrl="/account/login"
reasonPrompt="Subscribe to continue reading."
subheadlineText="Subheadline"
usePortal={false}
/>
);

expect(screen.getByText("Subscribe to continue reading.")).not.toBeNull();
expect(screen.getByText("Already a subscriber?")).not.toBeNull();
expect(screen.getByText("Log In.").closest("a")).toHaveAttribute("href", "/account/login");

expect(screen.getByText("Headline")).not.toBeNull();
expect(screen.getByText("Subheadline")).not.toBeNull();
expect(screen.getByText("Subscribe").closest("a")).toHaveAttribute("href", "/account/signup");
});
});
Loading

0 comments on commit 945fbe3

Please sign in to comment.