mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 19:33:02 +03:00
🐛 Fixed members unable to unsubscribe from plan if hidden in Portal (#17251)
refs TryGhost/Product#3563 - For a member on a paid plan, which had subsequently been hidden from portal, the member was unable to unsubscribe/change plan because the 'Change' button was hidden - This change restores the 'Change' button for members on a paid plan, even if the plan is hidden from portal - This change also makes some modifications to the 'Change Plan' page, like showing the current active plan even if it is hidden, and displays a message to comped members to contact support if they want to change their plan --------- Co-authored-by: Sodbileg Gansukh <sodbileg.gansukh@gmail.com>
This commit is contained in:
parent
a17b2f024e
commit
96b678a20d
@ -109,6 +109,12 @@ export const GlobalStyles = `
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
p a {
|
||||
font-weight: 500;
|
||||
color: var(--brandcolor);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, {useContext, useEffect, useState} from 'react';
|
||||
import {ReactComponent as LoaderIcon} from '../../images/icons/loader.svg';
|
||||
import {ReactComponent as CheckmarkIcon} from '../../images/icons/checkmark.svg';
|
||||
import {getCurrencySymbol, getPriceString, getStripeAmount, getMemberActivePrice, getProductFromPrice, getFreeTierTitle, getFreeTierDescription, getFreeProduct, getFreeProductBenefits, formatNumber, isCookiesDisabled, hasOnlyFreeProduct, isMemberActivePrice, hasFreeTrialTier} from '../../utils/helpers';
|
||||
import {getCurrencySymbol, getPriceString, getStripeAmount, getMemberActivePrice, getProductFromPrice, getFreeTierTitle, getFreeTierDescription, getFreeProduct, getFreeProductBenefits, getSupportAddress, formatNumber, isCookiesDisabled, hasOnlyFreeProduct, isMemberActivePrice, hasFreeTrialTier, isComplimentaryMember} from '../../utils/helpers';
|
||||
import AppContext from '../../AppContext';
|
||||
import calculateDiscount from '../../utils/discount';
|
||||
import Interpolate from '@doist/react-interpolate';
|
||||
@ -913,7 +913,7 @@ function getActiveInterval({portalPlans, selectedInterval = 'year'}) {
|
||||
}
|
||||
|
||||
function ProductsSection({onPlanSelect, products, type = null, handleChooseSignup, errors}) {
|
||||
const {site} = useContext(AppContext);
|
||||
const {site, member, t} = useContext(AppContext);
|
||||
const {portal_plans: portalPlans} = site;
|
||||
const defaultInterval = getActiveInterval({portalPlans});
|
||||
|
||||
@ -924,6 +924,8 @@ function ProductsSection({onPlanSelect, products, type = null, handleChooseSignu
|
||||
const selectedPrice = getSelectedPrice({products, selectedInterval, selectedProduct});
|
||||
const activeInterval = getActiveInterval({portalPlans, selectedInterval});
|
||||
|
||||
const isComplimentary = isComplimentaryMember({member});
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedProduct(defaultProductId);
|
||||
}, [defaultProductId]);
|
||||
@ -937,7 +939,16 @@ function ProductsSection({onPlanSelect, products, type = null, handleChooseSignu
|
||||
}
|
||||
|
||||
if (products.length === 0) {
|
||||
return null;
|
||||
if (isComplimentary) {
|
||||
const supportAddress = getSupportAddress({site});
|
||||
return (
|
||||
<p style={{textAlign: 'center'}}>
|
||||
{t('Please contact {{supportAddress}} to adjust your complimentary subscription.', {supportAddress})}
|
||||
</p>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
let className = 'gh-portal-products';
|
||||
@ -1091,7 +1102,7 @@ function ChangeProductCard({product, onPlanSelect}) {
|
||||
|
||||
function ChangeProductCards({products, onPlanSelect}) {
|
||||
return products.map((product) => {
|
||||
if (product.id === 'free') {
|
||||
if (!product || product.id === 'free') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
|
@ -1,5 +1,5 @@
|
||||
import AppContext from '../../../../AppContext';
|
||||
import {allowCompMemberUpgrade, getCompExpiry, getMemberSubscription, getMemberTierName, getUpdatedOfferPrice, hasMultipleProductsFeature, hasOnlyFreePlan, isComplimentaryMember, isInThePast, subscriptionHasFreeTrial} from '../../../../utils/helpers';
|
||||
import {allowCompMemberUpgrade, getCompExpiry, getMemberSubscription, getMemberTierName, getUpdatedOfferPrice, hasMultipleProductsFeature, hasOnlyFreePlan, isComplimentaryMember, isPaidMember, isInThePast, subscriptionHasFreeTrial} from '../../../../utils/helpers';
|
||||
import {getDateString} from '../../../../utils/date-time';
|
||||
import {ReactComponent as LoaderIcon} from '../../../../images/icons/loader.svg';
|
||||
import {ReactComponent as OfferTagIcon} from '../../../../images/icons/offer-tag.svg';
|
||||
@ -83,9 +83,9 @@ const PaidAccountActions = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const PlanUpdateButton = ({isComplimentary}) => {
|
||||
const PlanUpdateButton = ({isComplimentary, isPaid}) => {
|
||||
const hideUpgrade = allowCompMemberUpgrade({member}) ? false : isComplimentary;
|
||||
if (hideUpgrade || hasOnlyFreePlan({site})) {
|
||||
if (hideUpgrade || (hasOnlyFreePlan({site}) && !isPaid)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
@ -138,6 +138,8 @@ const PaidAccountActions = () => {
|
||||
|
||||
const subscription = getMemberSubscription({member});
|
||||
const isComplimentary = isComplimentaryMember({member});
|
||||
const isPaid = isPaidMember({member});
|
||||
const isCancelled = subscription?.cancel_at_period_end;
|
||||
if (subscription || isComplimentary) {
|
||||
const {
|
||||
price,
|
||||
@ -160,7 +162,7 @@ const PaidAccountActions = () => {
|
||||
<h3>{planLabel}</h3>
|
||||
<PlanLabel price={price} isComplimentary={isComplimentary} subscription={subscription} />
|
||||
</div>
|
||||
<PlanUpdateButton isComplimentary={isComplimentary} />
|
||||
<PlanUpdateButton isComplimentary={isComplimentary} isPaid={isPaid} isCancelled={isCancelled} />
|
||||
</section>
|
||||
<BillingSection isComplimentary={isComplimentary} defaultCardLast4={defaultCardLast4} />
|
||||
</>
|
||||
|
@ -5,7 +5,7 @@ import CloseButton from '../common/CloseButton';
|
||||
import BackButton from '../common/BackButton';
|
||||
import {MultipleProductsPlansSection} from '../common/PlansSection';
|
||||
import {getDateString} from '../../utils/date-time';
|
||||
import {allowCompMemberUpgrade, formatNumber, getAvailablePrices, getFilteredPrices, getMemberActivePrice, getMemberSubscription, getPriceFromSubscription, getProductFromPrice, getSubscriptionFromId, getUpgradeProducts, hasMultipleProductsFeature, isComplimentaryMember, isPaidMember} from '../../utils/helpers';
|
||||
import {allowCompMemberUpgrade, formatNumber, getAvailablePrices, getFilteredPrices, getMemberActivePrice, getMemberActiveProduct, getMemberSubscription, getPriceFromSubscription, getProductFromPrice, getSubscriptionFromId, getUpgradeProducts, hasMultipleProductsFeature, isComplimentaryMember, isPaidMember} from '../../utils/helpers';
|
||||
import Interpolate from '@doist/react-interpolate';
|
||||
import {SYNTAX_I18NEXT} from '@doist/react-interpolate';
|
||||
|
||||
@ -225,9 +225,11 @@ const ChangePlanSection = ({plans, selectedPlan, onPlanSelect, onCancelSubscript
|
||||
function PlansOrProductSection({showLabel, plans, selectedPlan, onPlanSelect, onPlanCheckout, changePlan = false}) {
|
||||
const {site, member} = useContext(AppContext);
|
||||
const products = getUpgradeProducts({site, member});
|
||||
const isComplimentary = isComplimentaryMember({member});
|
||||
const activeProduct = getMemberActiveProduct({member, site});
|
||||
return (
|
||||
<MultipleProductsPlansSection
|
||||
products={products}
|
||||
products={products.length > 0 || isComplimentary ? products : [activeProduct]}
|
||||
selectedPlan={selectedPlan}
|
||||
changePlan={changePlan}
|
||||
onPlanSelect={onPlanSelect}
|
||||
|
@ -137,6 +137,15 @@ export function getMemberActivePrice({member}) {
|
||||
return getPriceFromSubscription({subscription});
|
||||
}
|
||||
|
||||
export function getMemberActiveProduct({member, site}) {
|
||||
const subscription = getMemberSubscription({member});
|
||||
const price = getPriceFromSubscription({subscription});
|
||||
const allProducts = getAllProductsForSite({site});
|
||||
return allProducts.find((product) => {
|
||||
return product.id === price?.product.product_id;
|
||||
});
|
||||
}
|
||||
|
||||
export function isMemberActivePrice({priceId, site, member}) {
|
||||
const activePrice = getMemberActivePrice({member});
|
||||
const {tierId, cadence} = getProductCadenceFromPrice({site, priceId});
|
||||
|
@ -91,6 +91,7 @@
|
||||
"Plan checkout was cancelled.": "Notification for when a plan checkout was cancelled",
|
||||
"Plan upgrade was cancelled.": "Notification for when a plan upgrade was cancelled",
|
||||
"Please confirm your email address with this link:": "Descriptive text in signup emails, right before the button members click to confirm their address",
|
||||
"Please contact {{supportAddress}} to adjust your complimentary subscription.": "A message to comped members when trying to change their subscription, but no other paid plans are available.",
|
||||
"Please enter a valid email address": "Err message when an email address is invalid",
|
||||
"Please fill in required fields": "Error message when a required field is missing",
|
||||
"Price": "A label to indicate price of a tier",
|
||||
|
@ -87,6 +87,7 @@
|
||||
"Plan checkout was cancelled.": "",
|
||||
"Plan upgrade was cancelled.": "",
|
||||
"Please fill in required fields": "",
|
||||
"Please contact {{supportAddress}} to adjust your complimentary subscription.": "",
|
||||
"Price": "",
|
||||
"Re-enable emails": "",
|
||||
"Renews at {{price}}.": "",
|
||||
|
Loading…
Reference in New Issue
Block a user