mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 20:03:12 +03:00
Split AccountHomePage file on separate files
closes TryGhost/Team#2256 - Moved components to separate file for better navigation.
This commit is contained in:
parent
991e628022
commit
69c2af1ffe
@ -1,615 +0,0 @@
|
|||||||
import AppContext from '../../AppContext';
|
|
||||||
import MemberAvatar from '../common/MemberGravatar';
|
|
||||||
import ActionButton from '../common/ActionButton';
|
|
||||||
import CloseButton from '../common/CloseButton';
|
|
||||||
import Switch from '../common/Switch';
|
|
||||||
import {allowCompMemberUpgrade, getCompExpiry, getMemberSubscription, getMemberTierName, getSiteNewsletters, getSupportAddress, getUpdatedOfferPrice, hasCommentsEnabled, hasMultipleNewsletters, hasMultipleProductsFeature, hasOnlyFreePlan, isComplimentaryMember, 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';
|
|
||||||
import {useContext} from 'react';
|
|
||||||
|
|
||||||
const React = require('react');
|
|
||||||
|
|
||||||
export const AccountHomePageStyles = `
|
|
||||||
.gh-portal-account-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
margin: 0 0 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-account-header .gh-portal-avatar {
|
|
||||||
margin: 6px 0 8px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-account-data {
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer.gh-portal-account-footer {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-account-footer.paid {
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-account-footermenu {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-account-footerright {
|
|
||||||
display: flex;
|
|
||||||
flex-grow: 1;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-account-footermenu li {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-account-footermenu li:last-of-type {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-freeaccount-newsletter {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-freeaccount-newsletter .label {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-free-ctatext {
|
|
||||||
margin-top: -12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-cancelcontinue-container {
|
|
||||||
margin: 24px 0 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-billing-button-loader {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
margin-right: -3px;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-product-icon {
|
|
||||||
width: 52px;
|
|
||||||
margin-right: 12px;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-account-discountcontainer {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-account-old-price {
|
|
||||||
text-decoration: line-through;
|
|
||||||
color: var(--grey9) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-account-tagicon {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
color: var(--brandcolor);
|
|
||||||
margin-right: 5px;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 390px) {
|
|
||||||
.gh-portal-account-footer {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 340px) {
|
|
||||||
.gh-portal-account-footer {
|
|
||||||
padding: 0 !important;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gh-portal-account-footer .gh-portal-account-footerright {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const UserAvatar = ({avatar, brandColor}) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MemberAvatar gravatar={avatar} style={{userIcon: {color: brandColor, width: '56px', height: '56px', padding: '2px'}}} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AccountFooter = ({onClose, handleSignout, supportAddress = ''}) => {
|
|
||||||
const supportAddressMail = `mailto:${supportAddress}`;
|
|
||||||
return (
|
|
||||||
<footer className='gh-portal-account-footer'>
|
|
||||||
<ul className='gh-portal-account-footermenu'>
|
|
||||||
<li><button className='gh-portal-btn' name='logout' aria-label='logout' onClick={e => handleSignout(e)}>Sign out</button></li>
|
|
||||||
</ul>
|
|
||||||
<div className='gh-portal-account-footerright'>
|
|
||||||
<ul className='gh-portal-account-footermenu'>
|
|
||||||
<li><a className='gh-portal-btn gh-portal-btn-branded' href={supportAddressMail} onClick={() => {
|
|
||||||
supportAddressMail && window.open(supportAddressMail);
|
|
||||||
}}>Contact support</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const UserHeader = () => {
|
|
||||||
const {member, brandColor} = useContext(AppContext);
|
|
||||||
const avatar = member.avatar_image;
|
|
||||||
return (
|
|
||||||
<header className='gh-portal-account-header'>
|
|
||||||
<UserAvatar avatar={avatar} brandColor={brandColor} />
|
|
||||||
<h2 className="gh-portal-main-title">Your account</h2>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function getOfferLabel({offer, price, subscriptionStartDate}) {
|
|
||||||
let offerLabel = '';
|
|
||||||
|
|
||||||
if (offer) {
|
|
||||||
const discountDuration = offer.duration;
|
|
||||||
let durationLabel = '';
|
|
||||||
if (discountDuration === 'forever') {
|
|
||||||
durationLabel = `Forever`;
|
|
||||||
} else if (discountDuration === 'repeating') {
|
|
||||||
const durationInMonths = offer.duration_in_months || 0;
|
|
||||||
let offerStartDate = new Date(subscriptionStartDate);
|
|
||||||
let offerEndDate = new Date(offerStartDate.setMonth(offerStartDate.getMonth() + durationInMonths));
|
|
||||||
durationLabel = `Ends ${getDateString(offerEndDate)}`;
|
|
||||||
}
|
|
||||||
offerLabel = `${getUpdatedOfferPrice({offer, price, useFormatted: true})}/${price.interval}${durationLabel ? ` — ${durationLabel}` : ``}`;
|
|
||||||
}
|
|
||||||
return offerLabel;
|
|
||||||
}
|
|
||||||
|
|
||||||
function FreeTrialLabel({subscription, priceLabel}) {
|
|
||||||
if (subscriptionHasFreeTrial({sub: subscription})) {
|
|
||||||
const trialEnd = getDateString(subscription.trial_end_at);
|
|
||||||
return (
|
|
||||||
<p className="gh-portal-account-discountcontainer">
|
|
||||||
<div>
|
|
||||||
<span>Free Trial – Ends {trialEnd}</span>
|
|
||||||
{/* <span>{getSubFreeTrialDaysLeft({sub: subscription})} days left</span> */}
|
|
||||||
</div>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PaidAccountActions = () => {
|
|
||||||
const {member, site, onAction} = useContext(AppContext);
|
|
||||||
|
|
||||||
const onEditBilling = () => {
|
|
||||||
const subscription = getMemberSubscription({member});
|
|
||||||
onAction('editBilling', {subscriptionId: subscription.id});
|
|
||||||
};
|
|
||||||
|
|
||||||
const openUpdatePlan = () => {
|
|
||||||
const {is_stripe_configured: isStripeConfigured} = site;
|
|
||||||
if (isStripeConfigured) {
|
|
||||||
onAction('switchPage', {
|
|
||||||
page: 'accountPlan',
|
|
||||||
lastPage: 'accountHome'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const PlanLabel = ({price, isComplimentary, subscription}) => {
|
|
||||||
const {
|
|
||||||
offer,
|
|
||||||
start_date: startDate
|
|
||||||
} = subscription || {};
|
|
||||||
let label = '';
|
|
||||||
if (price) {
|
|
||||||
const {amount = 0, currency, interval} = price;
|
|
||||||
label = `${Intl.NumberFormat('en', {currency, style: 'currency'}).format(amount / 100)}/${interval}`;
|
|
||||||
}
|
|
||||||
let offerLabelStr = getOfferLabel({price, offer, subscriptionStartDate: startDate});
|
|
||||||
const compExpiry = getCompExpiry({member});
|
|
||||||
if (isComplimentary) {
|
|
||||||
if (compExpiry) {
|
|
||||||
label = `Complimentary - Expires ${compExpiry}`;
|
|
||||||
} else {
|
|
||||||
label = label ? `Complimentary (${label})` : `Complimentary`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let oldPriceClassName = '';
|
|
||||||
if (offerLabelStr) {
|
|
||||||
oldPriceClassName = 'gh-portal-account-old-price';
|
|
||||||
}
|
|
||||||
const OfferLabel = () => {
|
|
||||||
if (offerLabelStr) {
|
|
||||||
return (
|
|
||||||
<p className="gh-portal-account-discountcontainer">
|
|
||||||
<OfferTagIcon className="gh-portal-account-tagicon" />
|
|
||||||
<span>{offerLabelStr}</span>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const hasFreeTrial = subscriptionHasFreeTrial({sub: subscription});
|
|
||||||
if (hasFreeTrial) {
|
|
||||||
oldPriceClassName = 'gh-portal-account-old-price';
|
|
||||||
}
|
|
||||||
if (hasFreeTrial) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p className={oldPriceClassName}>
|
|
||||||
{label}
|
|
||||||
</p>
|
|
||||||
<FreeTrialLabel subscription={subscription} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p className={oldPriceClassName}>
|
|
||||||
{label}
|
|
||||||
</p>
|
|
||||||
<OfferLabel />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const PlanUpdateButton = ({isComplimentary}) => {
|
|
||||||
const hideUpgrade = allowCompMemberUpgrade({member}) ? false : isComplimentary;
|
|
||||||
if (hideUpgrade || hasOnlyFreePlan({site})) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<button className='gh-portal-btn gh-portal-btn-list' onClick={e => openUpdatePlan(e)}>Change</button>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const CardLabel = ({defaultCardLast4}) => {
|
|
||||||
if (defaultCardLast4) {
|
|
||||||
const label = `**** **** **** ${defaultCardLast4}`;
|
|
||||||
return (
|
|
||||||
<p>
|
|
||||||
{label}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const BillingSection = ({defaultCardLast4, isComplimentary}) => {
|
|
||||||
const {action} = useContext(AppContext);
|
|
||||||
const label = action === 'editBilling:running' ? (
|
|
||||||
<LoaderIcon className='gh-portal-billing-button-loader' />
|
|
||||||
) : 'Update';
|
|
||||||
if (isComplimentary) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<div className='gh-portal-list-detail'>
|
|
||||||
<h3>Billing info</h3>
|
|
||||||
<CardLabel defaultCardLast4={defaultCardLast4} />
|
|
||||||
</div>
|
|
||||||
<button className='gh-portal-btn gh-portal-btn-list' onClick={e => onEditBilling(e)}>{label}</button>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const subscription = getMemberSubscription({member});
|
|
||||||
const isComplimentary = isComplimentaryMember({member});
|
|
||||||
if (subscription || isComplimentary) {
|
|
||||||
const {
|
|
||||||
price,
|
|
||||||
default_payment_card_last4: defaultCardLast4
|
|
||||||
} = subscription || {};
|
|
||||||
let planLabel = 'Plan';
|
|
||||||
|
|
||||||
// Show name of tiers if there are multiple tiers
|
|
||||||
if (hasMultipleProductsFeature({site}) && getMemberTierName({member})) {
|
|
||||||
planLabel = getMemberTierName({member});
|
|
||||||
}
|
|
||||||
// const hasFreeTrial = subscriptionHasFreeTrial({sub: subscription});
|
|
||||||
// if (hasFreeTrial) {
|
|
||||||
// planLabel += ' (Free Trial)';
|
|
||||||
// }
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<section>
|
|
||||||
<div className='gh-portal-list-detail'>
|
|
||||||
<h3>{planLabel}</h3>
|
|
||||||
<PlanLabel price={price} isComplimentary={isComplimentary} subscription={subscription} />
|
|
||||||
</div>
|
|
||||||
<PlanUpdateButton isComplimentary={isComplimentary} />
|
|
||||||
</section>
|
|
||||||
<BillingSection isComplimentary={isComplimentary} defaultCardLast4={defaultCardLast4} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AccountActions = () => {
|
|
||||||
const {member, onAction} = useContext(AppContext);
|
|
||||||
const {name, email} = member;
|
|
||||||
|
|
||||||
const openEditProfile = () => {
|
|
||||||
onAction('switchPage', {
|
|
||||||
page: 'accountProfile',
|
|
||||||
lastPage: 'accountHome'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className='gh-portal-list'>
|
|
||||||
<section>
|
|
||||||
<div className='gh-portal-list-detail'>
|
|
||||||
<h3>{(name ? name : 'Account')}</h3>
|
|
||||||
<p>{email}</p>
|
|
||||||
</div>
|
|
||||||
<button className='gh-portal-btn gh-portal-btn-list' onClick={e => openEditProfile(e)}>Edit</button>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<PaidAccountActions />
|
|
||||||
<EmailPreferencesAction />
|
|
||||||
<EmailNewsletterAction />
|
|
||||||
</div>
|
|
||||||
{/* <ProductList openUpdatePlan={openUpdatePlan}></ProductList> */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function EmailNewsletterAction() {
|
|
||||||
const {member, site, onAction} = useContext(AppContext);
|
|
||||||
let {newsletters} = member;
|
|
||||||
|
|
||||||
if (hasMultipleNewsletters({site}) || hasCommentsEnabled({site})) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const subscribed = !!newsletters?.length;
|
|
||||||
let label = subscribed ? 'Subscribed' : 'Unsubscribed';
|
|
||||||
const onToggleSubscription = (e, sub) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const siteNewsletters = getSiteNewsletters({site});
|
|
||||||
const subscribedNewsletters = !member?.newsletters?.length ? siteNewsletters : [];
|
|
||||||
onAction('updateNewsletterPreference', {newsletters: subscribedNewsletters});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<div className='gh-portal-list-detail'>
|
|
||||||
<h3>Email newsletter</h3>
|
|
||||||
<p>{label}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Switch
|
|
||||||
id="default-newsletter-toggle"
|
|
||||||
onToggle={(e) => {
|
|
||||||
onToggleSubscription(e, subscribed);
|
|
||||||
}} checked={subscribed}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function EmailPreferencesAction() {
|
|
||||||
const {site, onAction} = useContext(AppContext);
|
|
||||||
if (!hasMultipleNewsletters({site}) && !hasCommentsEnabled({site})) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<section>
|
|
||||||
<div className='gh-portal-list-detail'>
|
|
||||||
<h3>Emails</h3>
|
|
||||||
<p>Update your preferences</p>
|
|
||||||
</div>
|
|
||||||
<button className='gh-portal-btn gh-portal-btn-list' onClick={(e) => {
|
|
||||||
onAction('switchPage', {
|
|
||||||
page: 'accountEmail',
|
|
||||||
lastPage: 'accountHome'
|
|
||||||
});
|
|
||||||
}}>Manage</button>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const SubscribeButton = () => {
|
|
||||||
const {site, action, brandColor, onAction} = useContext(AppContext);
|
|
||||||
const {is_stripe_configured: isStripeConfigured} = site;
|
|
||||||
|
|
||||||
if (!isStripeConfigured || hasOnlyFreePlan({site})) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const isRunning = ['checkoutPlan:running'].includes(action);
|
|
||||||
|
|
||||||
const openPlanPage = () => {
|
|
||||||
onAction('switchPage', {
|
|
||||||
page: 'accountPlan',
|
|
||||||
lastPage: 'accountHome'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<ActionButton
|
|
||||||
isRunning={isRunning}
|
|
||||||
label="View plans"
|
|
||||||
onClick={() => openPlanPage()}
|
|
||||||
brandColor={brandColor}
|
|
||||||
style={{width: '100%'}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AccountWelcome = () => {
|
|
||||||
const {member, site} = useContext(AppContext);
|
|
||||||
const {is_stripe_configured: isStripeConfigured} = site;
|
|
||||||
|
|
||||||
if (!isStripeConfigured || hasOnlyFreePlan({site})) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const subscription = getMemberSubscription({member});
|
|
||||||
const isComplimentary = isComplimentaryMember({member});
|
|
||||||
if (isComplimentary && !subscription) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (subscription) {
|
|
||||||
const currentPeriodEnd = subscription?.current_period_end;
|
|
||||||
if (isComplimentary && getCompExpiry({member})) {
|
|
||||||
const expiryDate = getCompExpiry({member});
|
|
||||||
const expiryAt = getDateString(expiryDate);
|
|
||||||
return (
|
|
||||||
<div className='gh-portal-section'>
|
|
||||||
<p className='gh-portal-text-center gh-portal-free-ctatext'>Your subscription will expire on {expiryAt}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (subscription?.cancel_at_period_end) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subscriptionHasFreeTrial({sub: subscription})) {
|
|
||||||
const trialEnd = getDateString(subscription.trial_end_at);
|
|
||||||
return (
|
|
||||||
<div className='gh-portal-section'>
|
|
||||||
<p className='gh-portal-text-center gh-portal-free-ctatext'>Your subscription will start on {trialEnd}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className='gh-portal-section'>
|
|
||||||
<p className='gh-portal-text-center gh-portal-free-ctatext'>Your subscription will renew on {getDateString(currentPeriodEnd)}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='gh-portal-section'>
|
|
||||||
<p className='gh-portal-text-center gh-portal-free-ctatext'>You currently have a free membership, upgrade to a paid subscription for full access.</p>
|
|
||||||
<SubscribeButton />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ContinueSubscriptionButton = () => {
|
|
||||||
const {member, onAction, action, brandColor} = useContext(AppContext);
|
|
||||||
const subscription = getMemberSubscription({member});
|
|
||||||
if (!subscription) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// To show only continue button and not cancellation
|
|
||||||
if (!subscription.cancel_at_period_end) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const label = subscription.cancel_at_period_end ? 'Continue subscription' : 'Cancel subscription';
|
|
||||||
const isRunning = ['cancelSubscription:running'].includes(action);
|
|
||||||
const disabled = (isRunning) ? true : false;
|
|
||||||
const isPrimary = !!subscription.cancel_at_period_end;
|
|
||||||
|
|
||||||
const CancelNotice = () => {
|
|
||||||
if (!subscription.cancel_at_period_end) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const currentPeriodEnd = subscription.current_period_end;
|
|
||||||
return (
|
|
||||||
<p className='gh-portal-text-center gh-portal-free-ctatext'>Your subscription will expire on {getDateString(currentPeriodEnd)}</p>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='gh-portal-cancelcontinue-container'>
|
|
||||||
<CancelNotice />
|
|
||||||
<ActionButton
|
|
||||||
onClick={(e) => {
|
|
||||||
onAction('continueSubscription', {
|
|
||||||
subscriptionId: subscription.id
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
isRunning={isRunning}
|
|
||||||
disabled={disabled}
|
|
||||||
isPrimary={isPrimary}
|
|
||||||
brandColor={brandColor}
|
|
||||||
label={label}
|
|
||||||
style={{
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AccountMain = () => {
|
|
||||||
return (
|
|
||||||
<div className='gh-portal-content gh-portal-account-main'>
|
|
||||||
<CloseButton />
|
|
||||||
<UserHeader />
|
|
||||||
<section className='gh-portal-account-data'>
|
|
||||||
<AccountWelcome />
|
|
||||||
<ContinueSubscriptionButton />
|
|
||||||
<AccountActions />
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class AccountHomePage extends React.Component {
|
|
||||||
static contextType = AppContext;
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const {member} = this.context;
|
|
||||||
if (!member) {
|
|
||||||
this.context.onAction('switchPage', {
|
|
||||||
page: 'signin'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSignout(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
this.context.onAction('signout');
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {member, site} = this.context;
|
|
||||||
const supportAddress = getSupportAddress({site});
|
|
||||||
if (!member) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className='gh-portal-account-wrapper'>
|
|
||||||
<AccountMain />
|
|
||||||
<AccountFooter
|
|
||||||
onClose={() => this.context.onAction('closePopup')}
|
|
||||||
handleSignout={e => this.handleSignout(e)}
|
|
||||||
supportAddress={supportAddress}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import AppContext from 'AppContext';
|
||||||
|
import {getSupportAddress} from 'utils/helpers';
|
||||||
|
|
||||||
|
import AccountFooter from './components/AccountFooter';
|
||||||
|
import AccountMain from './components/AccountMain';
|
||||||
|
|
||||||
|
export default class AccountHomePage extends React.Component {
|
||||||
|
static contextType = AppContext;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const {member} = this.context;
|
||||||
|
if (!member) {
|
||||||
|
this.context.onAction('switchPage', {
|
||||||
|
page: 'signin'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSignout(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.context.onAction('signout');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {member, site} = this.context;
|
||||||
|
const supportAddress = getSupportAddress({site});
|
||||||
|
if (!member) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='gh-portal-account-wrapper'>
|
||||||
|
<AccountMain />
|
||||||
|
<AccountFooter
|
||||||
|
onClose={() => this.context.onAction('closePopup')}
|
||||||
|
handleSignout={e => this.handleSignout(e)}
|
||||||
|
supportAddress={supportAddress}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {render, fireEvent} from '../../utils/test-utils';
|
import {render, fireEvent} from 'utils/test-utils';
|
||||||
import AccountHomePage from './AccountHomePage';
|
import AccountHomePage from './AccountHomePage';
|
||||||
|
|
||||||
const setup = (overrides) => {
|
const setup = (overrides) => {
|
@ -0,0 +1,39 @@
|
|||||||
|
import AppContext from 'AppContext';
|
||||||
|
import {useContext} from 'react';
|
||||||
|
|
||||||
|
import PaidAccountActions from './PaidAccountActions';
|
||||||
|
import EmailNewsletterAction from './EmailNewsletterAction';
|
||||||
|
import EmailPreferencesAction from './EmailPreferencesAction';
|
||||||
|
|
||||||
|
const AccountActions = () => {
|
||||||
|
const {member, onAction} = useContext(AppContext);
|
||||||
|
const {name, email} = member;
|
||||||
|
|
||||||
|
const openEditProfile = () => {
|
||||||
|
onAction('switchPage', {
|
||||||
|
page: 'accountProfile',
|
||||||
|
lastPage: 'accountHome'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className='gh-portal-list'>
|
||||||
|
<section>
|
||||||
|
<div className='gh-portal-list-detail'>
|
||||||
|
<h3>{(name ? name : 'Account')}</h3>
|
||||||
|
<p>{email}</p>
|
||||||
|
</div>
|
||||||
|
<button className='gh-portal-btn gh-portal-btn-list' onClick={e => openEditProfile(e)}>Edit</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<PaidAccountActions />
|
||||||
|
<EmailPreferencesAction />
|
||||||
|
<EmailNewsletterAction />
|
||||||
|
</div>
|
||||||
|
{/* <ProductList openUpdatePlan={openUpdatePlan}></ProductList> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountActions;
|
@ -0,0 +1,19 @@
|
|||||||
|
const AccountFooter = ({onClose, handleSignout, supportAddress = ''}) => {
|
||||||
|
const supportAddressMail = `mailto:${supportAddress}`;
|
||||||
|
return (
|
||||||
|
<footer className='gh-portal-account-footer'>
|
||||||
|
<ul className='gh-portal-account-footermenu'>
|
||||||
|
<li><button className='gh-portal-btn' name='logout' aria-label='logout' onClick={e => handleSignout(e)}>Sign out</button></li>
|
||||||
|
</ul>
|
||||||
|
<div className='gh-portal-account-footerright'>
|
||||||
|
<ul className='gh-portal-account-footermenu'>
|
||||||
|
<li><a className='gh-portal-btn gh-portal-btn-branded' href={supportAddressMail} onClick={() => {
|
||||||
|
supportAddressMail && window.open(supportAddressMail);
|
||||||
|
}}>Contact support</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountFooter;
|
@ -0,0 +1,22 @@
|
|||||||
|
import CloseButton from 'components/common/CloseButton';
|
||||||
|
|
||||||
|
import UserHeader from './UserHeader';
|
||||||
|
import AccountWelcome from './AccountWelcome';
|
||||||
|
import ContinueSubscriptionButton from './ContinueSubscriptionButton';
|
||||||
|
import AccountActions from './AccountActions';
|
||||||
|
|
||||||
|
const AccountMain = () => {
|
||||||
|
return (
|
||||||
|
<div className='gh-portal-content gh-portal-account-main'>
|
||||||
|
<CloseButton />
|
||||||
|
<UserHeader />
|
||||||
|
<section className='gh-portal-account-data'>
|
||||||
|
<AccountWelcome />
|
||||||
|
<ContinueSubscriptionButton />
|
||||||
|
<AccountActions />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountMain;
|
@ -0,0 +1,58 @@
|
|||||||
|
import AppContext from 'AppContext';
|
||||||
|
import {getCompExpiry, getMemberSubscription, hasOnlyFreePlan, isComplimentaryMember, subscriptionHasFreeTrial} from 'utils/helpers';
|
||||||
|
import {getDateString} from 'utils/date-time';
|
||||||
|
import {useContext} from 'react';
|
||||||
|
|
||||||
|
import SubscribeButton from './SubscribeButton';
|
||||||
|
|
||||||
|
const AccountWelcome = () => {
|
||||||
|
const {member, site} = useContext(AppContext);
|
||||||
|
const {is_stripe_configured: isStripeConfigured} = site;
|
||||||
|
|
||||||
|
if (!isStripeConfigured || hasOnlyFreePlan({site})) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const subscription = getMemberSubscription({member});
|
||||||
|
const isComplimentary = isComplimentaryMember({member});
|
||||||
|
if (isComplimentary && !subscription) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (subscription) {
|
||||||
|
const currentPeriodEnd = subscription?.current_period_end;
|
||||||
|
if (isComplimentary && getCompExpiry({member})) {
|
||||||
|
const expiryDate = getCompExpiry({member});
|
||||||
|
const expiryAt = getDateString(expiryDate);
|
||||||
|
return (
|
||||||
|
<div className='gh-portal-section'>
|
||||||
|
<p className='gh-portal-text-center gh-portal-free-ctatext'>Your subscription will expire on {expiryAt}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (subscription?.cancel_at_period_end) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscriptionHasFreeTrial({sub: subscription})) {
|
||||||
|
const trialEnd = getDateString(subscription.trial_end_at);
|
||||||
|
return (
|
||||||
|
<div className='gh-portal-section'>
|
||||||
|
<p className='gh-portal-text-center gh-portal-free-ctatext'>Your subscription will start on {trialEnd}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='gh-portal-section'>
|
||||||
|
<p className='gh-portal-text-center gh-portal-free-ctatext'>Your subscription will renew on {getDateString(currentPeriodEnd)}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='gh-portal-section'>
|
||||||
|
<p className='gh-portal-text-center gh-portal-free-ctatext'>You currently have a free membership, upgrade to a paid subscription for full access.</p>
|
||||||
|
<SubscribeButton />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AccountWelcome;
|
@ -0,0 +1,55 @@
|
|||||||
|
import AppContext from 'AppContext';
|
||||||
|
import ActionButton from 'components/common/ActionButton';
|
||||||
|
import {getMemberSubscription} from 'utils/helpers';
|
||||||
|
import {getDateString} from 'utils/date-time';
|
||||||
|
import {useContext} from 'react';
|
||||||
|
|
||||||
|
const ContinueSubscriptionButton = () => {
|
||||||
|
const {member, onAction, action, brandColor} = useContext(AppContext);
|
||||||
|
const subscription = getMemberSubscription({member});
|
||||||
|
if (!subscription) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To show only continue button and not cancellation
|
||||||
|
if (!subscription.cancel_at_period_end) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const label = subscription.cancel_at_period_end ? 'Continue subscription' : 'Cancel subscription';
|
||||||
|
const isRunning = ['cancelSubscription:running'].includes(action);
|
||||||
|
const disabled = (isRunning) ? true : false;
|
||||||
|
const isPrimary = !!subscription.cancel_at_period_end;
|
||||||
|
|
||||||
|
const CancelNotice = () => {
|
||||||
|
if (!subscription.cancel_at_period_end) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const currentPeriodEnd = subscription.current_period_end;
|
||||||
|
return (
|
||||||
|
<p className='gh-portal-text-center gh-portal-free-ctatext'>Your subscription will expire on {getDateString(currentPeriodEnd)}</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='gh-portal-cancelcontinue-container'>
|
||||||
|
<CancelNotice />
|
||||||
|
<ActionButton
|
||||||
|
onClick={(e) => {
|
||||||
|
onAction('continueSubscription', {
|
||||||
|
subscriptionId: subscription.id
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
isRunning={isRunning}
|
||||||
|
disabled={disabled}
|
||||||
|
isPrimary={isPrimary}
|
||||||
|
brandColor={brandColor}
|
||||||
|
label={label}
|
||||||
|
style={{
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ContinueSubscriptionButton;
|
@ -0,0 +1,40 @@
|
|||||||
|
import AppContext from 'AppContext';
|
||||||
|
import Switch from 'components/common/Switch';
|
||||||
|
import {getSiteNewsletters, hasCommentsEnabled, hasMultipleNewsletters} from 'utils/helpers';
|
||||||
|
import {useContext} from 'react';
|
||||||
|
|
||||||
|
function EmailNewsletterAction() {
|
||||||
|
const {member, site, onAction} = useContext(AppContext);
|
||||||
|
let {newsletters} = member;
|
||||||
|
|
||||||
|
if (hasMultipleNewsletters({site}) || hasCommentsEnabled({site})) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const subscribed = !!newsletters?.length;
|
||||||
|
let label = subscribed ? 'Subscribed' : 'Unsubscribed';
|
||||||
|
const onToggleSubscription = (e, sub) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const siteNewsletters = getSiteNewsletters({site});
|
||||||
|
const subscribedNewsletters = !member?.newsletters?.length ? siteNewsletters : [];
|
||||||
|
onAction('updateNewsletterPreference', {newsletters: subscribedNewsletters});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<div className='gh-portal-list-detail'>
|
||||||
|
<h3>Email newsletter</h3>
|
||||||
|
<p>{label}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Switch
|
||||||
|
id="default-newsletter-toggle"
|
||||||
|
onToggle={(e) => {
|
||||||
|
onToggleSubscription(e, subscribed);
|
||||||
|
}} checked={subscribed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmailNewsletterAction;
|
@ -0,0 +1,26 @@
|
|||||||
|
import AppContext from 'AppContext';
|
||||||
|
import {hasCommentsEnabled, hasMultipleNewsletters} from 'utils/helpers';
|
||||||
|
import {useContext} from 'react';
|
||||||
|
|
||||||
|
function EmailPreferencesAction() {
|
||||||
|
const {site, onAction} = useContext(AppContext);
|
||||||
|
if (!hasMultipleNewsletters({site}) && !hasCommentsEnabled({site})) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<div className='gh-portal-list-detail'>
|
||||||
|
<h3>Emails</h3>
|
||||||
|
<p>Update your preferences</p>
|
||||||
|
</div>
|
||||||
|
<button className='gh-portal-btn gh-portal-btn-list' onClick={(e) => {
|
||||||
|
onAction('switchPage', {
|
||||||
|
page: 'accountEmail',
|
||||||
|
lastPage: 'accountHome'
|
||||||
|
});
|
||||||
|
}}>Manage</button>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmailPreferencesAction;
|
@ -0,0 +1,196 @@
|
|||||||
|
import AppContext from 'AppContext';
|
||||||
|
import {allowCompMemberUpgrade, getCompExpiry, getMemberSubscription, getMemberTierName, getUpdatedOfferPrice, hasMultipleProductsFeature, hasOnlyFreePlan, isComplimentaryMember, 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';
|
||||||
|
import {useContext} from 'react';
|
||||||
|
|
||||||
|
const PaidAccountActions = () => {
|
||||||
|
const {member, site, onAction} = useContext(AppContext);
|
||||||
|
|
||||||
|
const onEditBilling = () => {
|
||||||
|
const subscription = getMemberSubscription({member});
|
||||||
|
onAction('editBilling', {subscriptionId: subscription.id});
|
||||||
|
};
|
||||||
|
|
||||||
|
const openUpdatePlan = () => {
|
||||||
|
const {is_stripe_configured: isStripeConfigured} = site;
|
||||||
|
if (isStripeConfigured) {
|
||||||
|
onAction('switchPage', {
|
||||||
|
page: 'accountPlan',
|
||||||
|
lastPage: 'accountHome'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const PlanLabel = ({price, isComplimentary, subscription}) => {
|
||||||
|
const {
|
||||||
|
offer,
|
||||||
|
start_date: startDate
|
||||||
|
} = subscription || {};
|
||||||
|
let label = '';
|
||||||
|
if (price) {
|
||||||
|
const {amount = 0, currency, interval} = price;
|
||||||
|
label = `${Intl.NumberFormat('en', {currency, style: 'currency'}).format(amount / 100)}/${interval}`;
|
||||||
|
}
|
||||||
|
let offerLabelStr = getOfferLabel({price, offer, subscriptionStartDate: startDate});
|
||||||
|
const compExpiry = getCompExpiry({member});
|
||||||
|
if (isComplimentary) {
|
||||||
|
if (compExpiry) {
|
||||||
|
label = `Complimentary - Expires ${compExpiry}`;
|
||||||
|
} else {
|
||||||
|
label = label ? `Complimentary (${label})` : `Complimentary`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let oldPriceClassName = '';
|
||||||
|
if (offerLabelStr) {
|
||||||
|
oldPriceClassName = 'gh-portal-account-old-price';
|
||||||
|
}
|
||||||
|
const OfferLabel = () => {
|
||||||
|
if (offerLabelStr) {
|
||||||
|
return (
|
||||||
|
<p className="gh-portal-account-discountcontainer">
|
||||||
|
<OfferTagIcon className="gh-portal-account-tagicon" />
|
||||||
|
<span>{offerLabelStr}</span>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasFreeTrial = subscriptionHasFreeTrial({sub: subscription});
|
||||||
|
if (hasFreeTrial) {
|
||||||
|
oldPriceClassName = 'gh-portal-account-old-price';
|
||||||
|
}
|
||||||
|
if (hasFreeTrial) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className={oldPriceClassName}>
|
||||||
|
{label}
|
||||||
|
</p>
|
||||||
|
<FreeTrialLabel subscription={subscription} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className={oldPriceClassName}>
|
||||||
|
{label}
|
||||||
|
</p>
|
||||||
|
<OfferLabel />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PlanUpdateButton = ({isComplimentary}) => {
|
||||||
|
const hideUpgrade = allowCompMemberUpgrade({member}) ? false : isComplimentary;
|
||||||
|
if (hideUpgrade || hasOnlyFreePlan({site})) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<button className='gh-portal-btn gh-portal-btn-list' onClick={e => openUpdatePlan(e)}>Change</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const CardLabel = ({defaultCardLast4}) => {
|
||||||
|
if (defaultCardLast4) {
|
||||||
|
const label = `**** **** **** ${defaultCardLast4}`;
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
{label}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const BillingSection = ({defaultCardLast4, isComplimentary}) => {
|
||||||
|
const {action} = useContext(AppContext);
|
||||||
|
const label = action === 'editBilling:running' ? (
|
||||||
|
<LoaderIcon className='gh-portal-billing-button-loader' />
|
||||||
|
) : 'Update';
|
||||||
|
if (isComplimentary) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section>
|
||||||
|
<div className='gh-portal-list-detail'>
|
||||||
|
<h3>Billing info</h3>
|
||||||
|
<CardLabel defaultCardLast4={defaultCardLast4} />
|
||||||
|
</div>
|
||||||
|
<button className='gh-portal-btn gh-portal-btn-list' onClick={e => onEditBilling(e)}>{label}</button>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const subscription = getMemberSubscription({member});
|
||||||
|
const isComplimentary = isComplimentaryMember({member});
|
||||||
|
if (subscription || isComplimentary) {
|
||||||
|
const {
|
||||||
|
price,
|
||||||
|
default_payment_card_last4: defaultCardLast4
|
||||||
|
} = subscription || {};
|
||||||
|
let planLabel = 'Plan';
|
||||||
|
|
||||||
|
// Show name of tiers if there are multiple tiers
|
||||||
|
if (hasMultipleProductsFeature({site}) && getMemberTierName({member})) {
|
||||||
|
planLabel = getMemberTierName({member});
|
||||||
|
}
|
||||||
|
// const hasFreeTrial = subscriptionHasFreeTrial({sub: subscription});
|
||||||
|
// if (hasFreeTrial) {
|
||||||
|
// planLabel += ' (Free Trial)';
|
||||||
|
// }
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section>
|
||||||
|
<div className='gh-portal-list-detail'>
|
||||||
|
<h3>{planLabel}</h3>
|
||||||
|
<PlanLabel price={price} isComplimentary={isComplimentary} subscription={subscription} />
|
||||||
|
</div>
|
||||||
|
<PlanUpdateButton isComplimentary={isComplimentary} />
|
||||||
|
</section>
|
||||||
|
<BillingSection isComplimentary={isComplimentary} defaultCardLast4={defaultCardLast4} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
function FreeTrialLabel({subscription, priceLabel}) {
|
||||||
|
if (subscriptionHasFreeTrial({sub: subscription})) {
|
||||||
|
const trialEnd = getDateString(subscription.trial_end_at);
|
||||||
|
return (
|
||||||
|
<p className="gh-portal-account-discountcontainer">
|
||||||
|
<div>
|
||||||
|
<span>Free Trial – Ends {trialEnd}</span>
|
||||||
|
{/* <span>{getSubFreeTrialDaysLeft({sub: subscription})} days left</span> */}
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOfferLabel({offer, price, subscriptionStartDate}) {
|
||||||
|
let offerLabel = '';
|
||||||
|
|
||||||
|
if (offer) {
|
||||||
|
const discountDuration = offer.duration;
|
||||||
|
let durationLabel = '';
|
||||||
|
if (discountDuration === 'forever') {
|
||||||
|
durationLabel = `Forever`;
|
||||||
|
} else if (discountDuration === 'repeating') {
|
||||||
|
const durationInMonths = offer.duration_in_months || 0;
|
||||||
|
let offerStartDate = new Date(subscriptionStartDate);
|
||||||
|
let offerEndDate = new Date(offerStartDate.setMonth(offerStartDate.getMonth() + durationInMonths));
|
||||||
|
durationLabel = `Ends ${getDateString(offerEndDate)}`;
|
||||||
|
}
|
||||||
|
offerLabel = `${getUpdatedOfferPrice({offer, price, useFormatted: true})}/${price.interval}${durationLabel ? ` — ${durationLabel}` : ``}`;
|
||||||
|
}
|
||||||
|
return offerLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaidAccountActions;
|
||||||
|
|
@ -0,0 +1,32 @@
|
|||||||
|
import AppContext from 'AppContext';
|
||||||
|
import ActionButton from 'components/common/ActionButton';
|
||||||
|
import {hasOnlyFreePlan} from 'utils/helpers';
|
||||||
|
import {useContext} from 'react';
|
||||||
|
|
||||||
|
const SubscribeButton = () => {
|
||||||
|
const {site, action, brandColor, onAction} = useContext(AppContext);
|
||||||
|
const {is_stripe_configured: isStripeConfigured} = site;
|
||||||
|
|
||||||
|
if (!isStripeConfigured || hasOnlyFreePlan({site})) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const isRunning = ['checkoutPlan:running'].includes(action);
|
||||||
|
|
||||||
|
const openPlanPage = () => {
|
||||||
|
onAction('switchPage', {
|
||||||
|
page: 'accountPlan',
|
||||||
|
lastPage: 'accountHome'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ActionButton
|
||||||
|
isRunning={isRunning}
|
||||||
|
label="View plans"
|
||||||
|
onClick={() => openPlanPage()}
|
||||||
|
brandColor={brandColor}
|
||||||
|
style={{width: '100%'}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubscribeButton;
|
@ -0,0 +1,16 @@
|
|||||||
|
import AppContext from 'AppContext';
|
||||||
|
import MemberAvatar from 'components/common/MemberGravatar';
|
||||||
|
import React, {useContext} from 'react';
|
||||||
|
|
||||||
|
const UserHeader = () => {
|
||||||
|
const {member, brandColor} = useContext(AppContext);
|
||||||
|
const avatar = member.avatar_image;
|
||||||
|
return (
|
||||||
|
<header className='gh-portal-account-header'>
|
||||||
|
<MemberAvatar gravatar={avatar} style={{userIcon: {color: brandColor, width: '56px', height: '56px', padding: '2px'}}} />
|
||||||
|
<h2 className="gh-portal-main-title">Your account</h2>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserHeader;
|
@ -1,6 +1,6 @@
|
|||||||
import SigninPage from './components/pages/SigninPage';
|
import SigninPage from './components/pages/SigninPage';
|
||||||
import SignupPage from './components/pages/SignupPage';
|
import SignupPage from './components/pages/SignupPage';
|
||||||
import AccountHomePage from './components/pages/AccountHomePage';
|
import AccountHomePage from './components/pages/AccountHomePage/AccountHomePage';
|
||||||
import MagicLinkPage from './components/pages/MagicLinkPage';
|
import MagicLinkPage from './components/pages/MagicLinkPage';
|
||||||
import LoadingPage from './components/pages/LoadingPage';
|
import LoadingPage from './components/pages/LoadingPage';
|
||||||
import AccountPlanPage from './components/pages/AccountPlanPage';
|
import AccountPlanPage from './components/pages/AccountPlanPage';
|
||||||
|
Loading…
Reference in New Issue
Block a user