1 ? ' long' : '')}>{currencySymbol}
0
@@ -651,6 +703,7 @@ function FreeProductCard({products, handleChooseSignup}) {
function ProductCard({product, products, selectedInterval, handleChooseSignup}) {
const {selectedProduct, setSelectedProduct} = useContext(ProductsContext);
const {action} = useContext(AppContext);
+ const trialDays = product.trial_days;
const cardClass = selectedProduct === product.id ? 'gh-portal-product-card checked' : 'gh-portal-product-card';
const noOfProducts = products?.filter((d) => {
@@ -668,6 +721,41 @@ function ProductCard({product, products, selectedInterval, handleChooseSignup})
productDescription = 'Full access';
}
+ if (freeTrialFlag) {
+ return (
+ <>
+
{
+ e.stopPropagation();
+ setSelectedProduct(product.id);
+ }}>
+
+
+
+
+ {productDescription}
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+
return (
<>
{
@@ -699,7 +787,7 @@ function ProductCard({product, products, selectedInterval, handleChooseSignup})
>
- );
+ );
}
function ProductCards({products, selectedInterval, handleChooseSignup}) {
@@ -715,7 +803,7 @@ function ProductCards({products, selectedInterval, handleChooseSignup}) {
});
}
-function YearlyDiscount({discount}) {
+function YearlyDiscount({discount, trialDays}) {
const {site} = useContext(AppContext);
const {portal_plans: portalPlans} = site;
@@ -723,6 +811,14 @@ function YearlyDiscount({discount}) {
return null;
}
+ if (freeTrialFlag) {
+ return (
+ <>
+
{discount}% discount
+ >
+ );
+ }
+
return (
<>
{discount}% discount
diff --git a/ghost/portal/src/components/pages/OfferPage.js b/ghost/portal/src/components/pages/OfferPage.js
index e1c1061303..875e8769fe 100644
--- a/ghost/portal/src/components/pages/OfferPage.js
+++ b/ghost/portal/src/components/pages/OfferPage.js
@@ -87,7 +87,7 @@ export const OfferPageStyles = ({site}) => {
min-height: 0;
}
-.offer .gh-portal-product-card .gh-portal-product-card-pricecontainer {
+.offer .gh-portal-product-card .gh-portal-product-card-pricecontainer:not(.offer-type-trial) {
margin-top: 0px;
}
@@ -304,8 +304,13 @@ export default class OfferPage extends React.Component {
renderSubmitButton() {
const {action, brandColor} = this.context;
+ const {pageData: offer} = this.context;
let label = 'Continue';
+ if (offer.type === 'trial') {
+ label = 'Start ' + offer.amount + '-day free trial';
+ }
+
let isRunning = false;
if (action === 'signup:running') {
label = 'Sending...';
@@ -458,9 +463,7 @@ export default class OfferPage extends React.Component {
renewsLabel = `Renews at ${originalPrice}.`;
}
if (discountDuration === 'trial') {
- return (
-
Then {getCurrencySymbol(price.currency)}{formatNumber(price.amount / 100)}/{price.interval}
- );
+ return null;
}
return (
{this.getOffAmount({offer})} off {durationLabel}. {renewsLabel}
@@ -483,9 +486,10 @@ export default class OfferPage extends React.Component {
renderUpdatedTierPrice({offer, currencyClass, updatedPrice, price}) {
if (offer.type === 'trial') {
return (
-
+
- {offer.amount} days free
+ {getCurrencySymbol(price.currency)}
+ {formatNumber(this.renderRoundedPrice(updatedPrice))}
);
diff --git a/ghost/portal/src/components/pages/SignupPage.js b/ghost/portal/src/components/pages/SignupPage.js
index e88f76fad9..9a0f480ac9 100644
--- a/ghost/portal/src/components/pages/SignupPage.js
+++ b/ghost/portal/src/components/pages/SignupPage.js
@@ -6,7 +6,7 @@ import NewsletterSelectionPage from './NewsletterSelectionPage';
import ProductsSection from '../common/ProductsSection';
import InputForm from '../common/InputForm';
import {ValidateInputForm} from '../../utils/form';
-import {getSiteProducts, getSitePrices, hasOnlyFreePlan, isInviteOnlySite, freeHasBenefitsOrDescription, hasOnlyFreeProduct, getFreeProductBenefits, getFreeTierDescription, hasFreeProductPrice, hasMultipleNewsletters} from '../../utils/helpers';
+import {getSiteProducts, getSitePrices, hasOnlyFreePlan, isInviteOnlySite, freeHasBenefitsOrDescription, hasOnlyFreeProduct, getFreeProductBenefits, getFreeTierDescription, hasFreeProductPrice, hasMultipleNewsletters, hasFreeTrialTier} from '../../utils/helpers';
import {ReactComponent as InvitationIcon} from '../../images/icons/invitation.svg';
const React = require('react');
@@ -193,6 +193,13 @@ footer.gh-portal-signup-footer.invite-only .gh-portal-signup-message {
margin-bottom: 12px;
}
+.gh-portal-free-trial-notification {
+ max-width: 480px;
+ text-align: center;
+ margin: 24px auto;
+ color: var(--grey4);
+}
+
@media (min-width: 480px) {
}
@@ -448,18 +455,31 @@ class SignupPage extends React.Component {
);
}
+ renderFreeTrialMessage() {
+ const {site} = this.context;
+ if (hasFreeTrialTier({site})) {
+ return (
+
After a free trial ends, you will be charged regular price for the tier you’ve chosen. You can always cancel before then.
+ );
+ }
+ return null;
+ }
+
renderLoginMessage() {
const {brandColor, onAction} = this.context;
return (
-
-
Already a member?
-
+
+ {this.renderFreeTrialMessage()}
+
+
Already a member?
+
+
);
}
diff --git a/ghost/portal/src/tests/SignupFlow.test.js b/ghost/portal/src/tests/SignupFlow.test.js
index e124ec1f60..b54c1d8cd8 100644
--- a/ghost/portal/src/tests/SignupFlow.test.js
+++ b/ghost/portal/src/tests/SignupFlow.test.js
@@ -672,7 +672,7 @@ describe('Signup', () => {
expect(nameInput).toHaveValue('Jamie Larsen');
fireEvent.click(tierContainer[0]);
- const labelText = popupIframeDocument.querySelector('.gh-portal-discount-label');
+ const labelText = popupIframeDocument.querySelector('.gh-portal-discount-label-trial');
await waitFor(() => {
expect(labelText).toBeInTheDocument();
});
diff --git a/ghost/portal/src/utils/helpers.js b/ghost/portal/src/utils/helpers.js
index 33633a5b2f..adf551befa 100644
--- a/ghost/portal/src/utils/helpers.js
+++ b/ghost/portal/src/utils/helpers.js
@@ -367,6 +367,13 @@ export function getSiteProducts({site, pageQuery}) {
return products;
}
+export function hasFreeTrialTier({site}) {
+ const tiers = getSiteProducts({site});
+ return tiers.some((tier) => {
+ return !!tier?.trial_days;
+ });
+}
+
export function getFreeProductBenefits({site}) {
const freeProduct = getFreeProduct({site});
return freeProduct?.benefits || [];