mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05:00
Updated account plan page to include confirmation screen
no issue Adds confirmation screen on plan change/cancel on the plans screen
This commit is contained in:
parent
856f56f04e
commit
51382bbee9
3 changed files with 177 additions and 30 deletions
|
@ -13,7 +13,7 @@ const DEV_MODE_DATA = {
|
|||
showPopup: true,
|
||||
site: Fixtures.site,
|
||||
member: Fixtures.member.paid,
|
||||
page: 'signup'
|
||||
page: 'accountPlan'
|
||||
};
|
||||
export default class App extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import ActionButton from '../common/ActionButton';
|
|||
import BackButton from '../common/BackButton';
|
||||
import PlansSection from '../common/PlansSection';
|
||||
import {getDateString} from '../../utils/date-time';
|
||||
import {getMemberSubscription, getSitePlans} from '../../utils/helpers';
|
||||
import {getMemberSubscription, getPlanFromSubscription, getSitePlans, getSubscriptionFromId} from '../../utils/helpers';
|
||||
|
||||
export const AccountPlanPageStyles = `
|
||||
.gh-portal-accountplans-main {
|
||||
|
@ -17,6 +17,16 @@ export const AccountPlanPageStyles = `
|
|||
|
||||
const React = require('react');
|
||||
|
||||
function getConfirmationPageTitle({confirmationType}) {
|
||||
if (confirmationType === 'changePlan') {
|
||||
return 'Change plan';
|
||||
} else if (confirmationType === 'cancel') {
|
||||
return 'Confirm cancellation';
|
||||
} else if (confirmationType === 'subscribe') {
|
||||
return 'Subscribe';
|
||||
}
|
||||
}
|
||||
|
||||
const GlobalError = ({message, style}) => {
|
||||
if (!message) {
|
||||
return null;
|
||||
|
@ -30,7 +40,7 @@ const GlobalError = ({message, style}) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const CancelContinueSubscription = ({member, onAction, action, brandColor, showOnlyContinue = false}) => {
|
||||
export const CancelContinueSubscription = ({member, onCancelContinueSubscription, onAction, action, brandColor, showOnlyContinue = false}) => {
|
||||
if (!member.paid) {
|
||||
return null;
|
||||
}
|
||||
|
@ -65,7 +75,11 @@ export const CancelContinueSubscription = ({member, onAction, action, brandColor
|
|||
<CancelNotice />
|
||||
<ActionButton
|
||||
onClick={(e) => {
|
||||
onAction('cancelSubscription', {
|
||||
// onAction('cancelSubscription', {
|
||||
// subscriptionId: subscription.id,
|
||||
// cancelAtPeriodEnd: !subscription.cancel_at_period_end
|
||||
// });
|
||||
onCancelContinueSubscription({
|
||||
subscriptionId: subscription.id,
|
||||
cancelAtPeriodEnd: !subscription.cancel_at_period_end
|
||||
});
|
||||
|
@ -83,8 +97,11 @@ export const CancelContinueSubscription = ({member, onAction, action, brandColor
|
|||
);
|
||||
};
|
||||
|
||||
const Header = ({member, brandColor, onBack}) => {
|
||||
const title = member.paid ? 'Choose Plan' : 'Choose your subscription';
|
||||
const Header = ({member, brandColor, onBack, showConfirmation, confirmationType}) => {
|
||||
let title = member.paid ? 'Choose Plan' : 'Choose your subscription';
|
||||
if (showConfirmation) {
|
||||
title = getConfirmationPageTitle({confirmationType});
|
||||
}
|
||||
return (
|
||||
<header className='gh-portal-detail-header'>
|
||||
<BackButton brandColor={brandColor} onClick={e => onBack(e)} />
|
||||
|
@ -127,11 +144,37 @@ const SubmitButton = ({member, action, plan, brandColor, onPlanCheckout}) => {
|
|||
);
|
||||
};
|
||||
|
||||
const PlanConfirmation = ({newPlan, onCancelConfirmation, onPlanCheckout}) => {
|
||||
return `Please confirm your new Plan ${newPlan}`;
|
||||
const PlanConfirmation = ({plan, type, brandColor, onConfirm}) => {
|
||||
let actionDescription = '';
|
||||
let confirmMessage = 'Are you sure ?';
|
||||
if (type === 'changePlan') {
|
||||
actionDescription = `You are switching to a ${plan.name} plan with pricing ${plan.currency} ${plan.price} / ${plan.type} `;
|
||||
} else if (type === 'subscribe') {
|
||||
actionDescription = `You are subscribing to a ${plan.name} plan with pricing ${plan.currency} ${plan.price} / ${plan.type} `;
|
||||
} else if (type === 'cancel') {
|
||||
actionDescription = `You are about to cancel your subscription for ${plan.currency} ${plan.price} ${plan.name} plan`;
|
||||
}
|
||||
const label = 'Confirm';
|
||||
return (
|
||||
<div>
|
||||
<div> {actionDescription} </div>
|
||||
<div> {confirmMessage} </div>
|
||||
<ActionButton
|
||||
onClick={e => onConfirm(e, plan)}
|
||||
isRunning={false}
|
||||
isPrimary={true}
|
||||
brandColor={brandColor}
|
||||
label={label}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '40px'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PlanChooser = ({plans, selectedPlan, errors, member, onAction, action, brandColor, onPlanSelect}) => {
|
||||
const PlanChooser = ({plans, selectedPlan, errors, member, onAction, onCancelContinueSubscription, action, brandColor, onPlanSelect}) => {
|
||||
const {global} = errors || {};
|
||||
return (
|
||||
<section>
|
||||
|
@ -144,24 +187,25 @@ const PlanChooser = ({plans, selectedPlan, errors, member, onAction, action, bra
|
|||
/>
|
||||
<GlobalError message={global} />
|
||||
</div>
|
||||
<CancelContinueSubscription {...{member, onAction, action, brandColor}} />
|
||||
<CancelContinueSubscription {...{member, onCancelContinueSubscription, action, brandColor}} />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
const PlanMain = ({
|
||||
plans, selectedPlan, newPlan, errors, member, onAction, action, brandColor,
|
||||
showConfirmation = false, onPlanSelect, onCancelConfirmation, onPlanCheckout
|
||||
plans, selectedPlan, confirmationPlan, confirmationType,
|
||||
errors, member, onAction, action, brandColor,
|
||||
showConfirmation = false, onPlanSelect, onConfirm, onCancelContinueSubscription
|
||||
}) => {
|
||||
if (!showConfirmation) {
|
||||
return (
|
||||
<PlanChooser
|
||||
{...{plans, selectedPlan, errors, member, onAction, action, brandColor, onPlanSelect}}
|
||||
{...{plans, selectedPlan, errors, member, onAction, onCancelContinueSubscription, action, brandColor, onPlanSelect}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<PlanConfirmation {...{newPlan, onCancelConfirmation, onPlanCheckout}}/>
|
||||
<PlanConfirmation {...{plan: confirmationPlan, type: confirmationType, onConfirm}}/>
|
||||
);
|
||||
};
|
||||
export default class AccountPlanPage extends React.Component {
|
||||
|
@ -172,13 +216,16 @@ export default class AccountPlanPage extends React.Component {
|
|||
const {member} = this.context;
|
||||
const {site} = this.context;
|
||||
this.plans = getSitePlans({site});
|
||||
let activePlan = this.getActivePlanName({member}) || this.plans[0].name;
|
||||
const activePlanExists = this.plans.some(d => d.name === activePlan);
|
||||
if (!activePlanExists) {
|
||||
activePlan = this.plans[0].name;
|
||||
}
|
||||
let activePlan = this.getActivePlan({member});
|
||||
const selectedPlan = activePlan ? this.plans.find((d) => {
|
||||
return (d.name === activePlan.name && d.price === activePlan.price && d.currency === activePlan.currency);
|
||||
}) : null;
|
||||
// const activePlanExists = this.plans.some(d => d.name === activePlan);
|
||||
// if (!activePlanExists) {
|
||||
// activePlan = this.plans[0].name;
|
||||
// }
|
||||
this.state = {
|
||||
plan: activePlan
|
||||
plan: selectedPlan ? selectedPlan.name : null
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -188,10 +235,22 @@ export default class AccountPlanPage extends React.Component {
|
|||
}
|
||||
|
||||
onBack(e) {
|
||||
this.context.onAction('back');
|
||||
if (this.state.showConfirmation) {
|
||||
this.cancelConfirmPage();
|
||||
} else {
|
||||
this.context.onAction('back');
|
||||
}
|
||||
}
|
||||
|
||||
onPlanCheckout(e) {
|
||||
cancelConfirmPage() {
|
||||
this.setState({
|
||||
showConfirmation: false,
|
||||
confirmationPlan: null,
|
||||
confirmationType: null
|
||||
});
|
||||
}
|
||||
|
||||
onPlanCheckoutOld(e) {
|
||||
e.preventDefault();
|
||||
this.setState((state) => {
|
||||
const errors = this.validateForm({state});
|
||||
|
@ -214,11 +273,31 @@ export default class AccountPlanPage extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
onPlanCheckout(e) {
|
||||
const {onAction, member} = this.context;
|
||||
const {confirmationPlan: plan, errors} = this.state;
|
||||
const hasFormErrors = (errors && Object.values(errors).filter(d => !!d).length > 0);
|
||||
if (!hasFormErrors) {
|
||||
if (member.paid) {
|
||||
const {subscriptions} = member;
|
||||
const subscriptionId = subscriptions[0].id;
|
||||
onAction('updateSubscription', {plan: plan.name, subscriptionId});
|
||||
} else {
|
||||
onAction('checkoutPlan', {plan: plan.name});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onPlanSelect(e, name) {
|
||||
const {member} = this.context;
|
||||
e.preventDefault();
|
||||
const confirmationPlan = this.plans.find(d => d.name === name);
|
||||
const activePlan = this.getActivePlanName({member});
|
||||
const confirmationType = activePlan ? 'changePlan' : 'subscribe';
|
||||
if (name !== this.state.plan) {
|
||||
this.setState({
|
||||
newPlan: name,
|
||||
confirmationPlan,
|
||||
confirmationType,
|
||||
showConfirmation: true
|
||||
});
|
||||
}
|
||||
|
@ -232,13 +311,49 @@ export default class AccountPlanPage extends React.Component {
|
|||
// }, 5);
|
||||
}
|
||||
|
||||
onCancelConfirmation() {
|
||||
this.setState({
|
||||
newPlan: null,
|
||||
showConfirmation: false
|
||||
onCancelContinueSubscription({subscriptionId, cancelAtPeriodEnd}) {
|
||||
const {member} = this.context;
|
||||
const subscription = getSubscriptionFromId({subscriptionId, member});
|
||||
const subscriptionPlan = getPlanFromSubscription({subscription});
|
||||
if (!cancelAtPeriodEnd) {
|
||||
this.context.onAction('cancelSubscription', {
|
||||
subscriptionId,
|
||||
cancelAtPeriodEnd
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
showConfirmation: true,
|
||||
confirmationPlan: subscriptionPlan,
|
||||
confirmationType: 'cancel'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onCancelSubscriptionConfirmation() {
|
||||
const {member} = this.context;
|
||||
const subscription = getMemberSubscription({member});
|
||||
if (!subscription) {
|
||||
return null;
|
||||
}
|
||||
this.context.onAction('cancelSubscription', {
|
||||
subscriptionId: subscription.id,
|
||||
cancelAtPeriodEnd: true
|
||||
});
|
||||
}
|
||||
|
||||
getActivePlan({member}) {
|
||||
if (member && member.paid && member.subscriptions[0]) {
|
||||
const {plan} = member.subscriptions[0];
|
||||
return {
|
||||
type: plan.interval,
|
||||
price: plan.amount / 100,
|
||||
currency: plan.currency_symbol,
|
||||
name: plan.nickname
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getActivePlanName({member}) {
|
||||
if (member && member.paid && member.subscriptions[0]) {
|
||||
const {plan} = member.subscriptions[0];
|
||||
|
@ -263,14 +378,26 @@ export default class AccountPlanPage extends React.Component {
|
|||
const plans = this.plans;
|
||||
const selectedPlan = this.state.plan;
|
||||
const errors = this.state.errors || {};
|
||||
const {showConfirmation, newPlan} = this.state;
|
||||
const {showConfirmation, confirmationPlan, confirmationType} = this.state;
|
||||
let onConfirm = () => {};
|
||||
if (confirmationType === 'cancel') {
|
||||
onConfirm = () => this.onCancelSubscriptionConfirmation();
|
||||
} else if (['changePlan', 'subscribe'].includes(confirmationType)) {
|
||||
onConfirm = e => this.onPlanCheckout(e);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className='gh-portal-content'>
|
||||
<Header member={member} brandColor={brandColor} onBack={e => this.onBack} />
|
||||
<Header
|
||||
member={member} brandColor={brandColor} onBack={e => this.onBack(e)}
|
||||
confirmationType = {confirmationType}
|
||||
showConfirmation = {showConfirmation}
|
||||
/>
|
||||
<PlanMain
|
||||
{...this.context}
|
||||
{...{plans, selectedPlan, showConfirmation, newPlan, errors}}
|
||||
{...{plans, selectedPlan, showConfirmation, confirmationPlan, confirmationType, onConfirm, errors}}
|
||||
onCancelSubscriptionConfirmation = {() => this.onCancelSubscriptionConfirmation()}
|
||||
onCancelContinueSubscription = {data => this.onCancelContinueSubscription(data)}
|
||||
onPlanSelect = {(e, name) => this.onPlanSelect(e, name)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -21,6 +21,26 @@ export function isPaidMember({member = {}}) {
|
|||
return (member && member.paid);
|
||||
}
|
||||
|
||||
export function getPlanFromSubscription({subscription}) {
|
||||
if (subscription && subscription.plan) {
|
||||
return {
|
||||
type: subscription.plan.interval,
|
||||
price: subscription.plan.amount / 100,
|
||||
currency: subscription.plan.currency_symbol,
|
||||
name: subscription.plan.nickname
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getSubscriptionFromId({member, subscriptionId}) {
|
||||
if (member.paid) {
|
||||
const subscriptions = member.subscriptions || [];
|
||||
return subscriptions.find(d => d.id === subscriptionId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getSitePlans({site = {}}) {
|
||||
const {plans} = site;
|
||||
const discount = CalculateDiscount(plans.monthly, plans.yearly);
|
||||
|
|
Loading…
Add table
Reference in a new issue