647 lines
25 KiB
React
647 lines
25 KiB
React
|
/* eslint-disable no-console */
|
||
|
// @flow
|
||
|
import React from 'react';
|
||
|
import moment from 'moment';
|
||
|
import Page from 'component/page';
|
||
|
import Spinner from 'component/spinner';
|
||
|
import { Lbryio } from 'lbryinc';
|
||
|
import { getStripeEnvironment } from 'util/stripe';
|
||
|
import * as ICONS from 'constants/icons';
|
||
|
import * as PAGES from 'constants/pages';
|
||
|
import * as MODALS from 'constants/modal_types';
|
||
|
import Card from 'component/common/card';
|
||
|
import MembershipSplash from 'component/membershipSplash';
|
||
|
import Button from 'component/button';
|
||
|
import ChannelSelector from 'component/channelSelector';
|
||
|
import PremiumBadge from 'component/common/premium-badge';
|
||
|
import useGetUserMemberships from 'effects/use-get-user-memberships';
|
||
|
import usePersistedState from 'effects/use-persisted-state';
|
||
|
|
||
|
let stripeEnvironment = getStripeEnvironment();
|
||
|
|
||
|
const isDev = process.env.NODE_ENV !== 'production';
|
||
|
|
||
|
// odysee channel information since the memberships are only for Odysee
|
||
|
const odyseeChannelId = '80d2590ad04e36fb1d077a9b9e3a8bba76defdf8';
|
||
|
const odyseeChannelName = '@odysee';
|
||
|
|
||
|
type Props = {
|
||
|
history: { action: string, push: (string) => void, replace: (string) => void },
|
||
|
location: { search: string, pathname: string },
|
||
|
totalBalance: ?number,
|
||
|
openModal: (string, {}) => void,
|
||
|
activeChannelClaim: ?ChannelClaim,
|
||
|
channels: ?Array<ChannelClaim>,
|
||
|
claimsByUri: { [string]: any },
|
||
|
fetchUserMemberships: (claimIdCsv: string) => void,
|
||
|
incognito: boolean,
|
||
|
updateUserOdyseeMembershipStatus: () => void,
|
||
|
user: ?User,
|
||
|
};
|
||
|
|
||
|
const OdyseeMembershipPage = (props: Props) => {
|
||
|
const {
|
||
|
openModal,
|
||
|
activeChannelClaim,
|
||
|
channels,
|
||
|
claimsByUri,
|
||
|
fetchUserMemberships,
|
||
|
updateUserOdyseeMembershipStatus,
|
||
|
incognito,
|
||
|
user,
|
||
|
} = props;
|
||
|
|
||
|
const shouldUseEuro = localStorage.getItem('gdprRequired');
|
||
|
let currencyToUse;
|
||
|
if (shouldUseEuro === 'true') {
|
||
|
currencyToUse = 'eur';
|
||
|
} else {
|
||
|
currencyToUse = 'usd';
|
||
|
}
|
||
|
|
||
|
const userChannelName = activeChannelClaim ? activeChannelClaim.name : '';
|
||
|
const userChannelClaimId = activeChannelClaim && activeChannelClaim.claim_id;
|
||
|
|
||
|
const [cardSaved, setCardSaved] = React.useState();
|
||
|
const [membershipOptions, setMembershipOptions] = React.useState();
|
||
|
const [userMemberships, setUserMemberships] = React.useState();
|
||
|
const [canceledMemberships, setCanceledMemberships] = React.useState();
|
||
|
const [activeMemberships, setActiveMemberships] = React.useState();
|
||
|
const [purchasedMemberships, setPurchasedMemberships] = React.useState([]);
|
||
|
const [hasShownModal, setHasShownModal] = React.useState(false);
|
||
|
const [shouldFetchUserMemberships, setFetchUserMemberships] = React.useState(true);
|
||
|
|
||
|
const [showHelp, setShowHelp] = usePersistedState('premium-help-seen', true);
|
||
|
|
||
|
const hasMembership = activeMemberships && activeMemberships.length > 0;
|
||
|
|
||
|
const channelUrls = channels && channels.map((channel) => channel.permanent_url);
|
||
|
|
||
|
// check if membership data for user is already fetched, if it's needed then fetch it
|
||
|
useGetUserMemberships(shouldFetchUserMemberships, channelUrls, claimsByUri, (value) => {
|
||
|
fetchUserMemberships(value);
|
||
|
setFetchUserMemberships(false);
|
||
|
});
|
||
|
|
||
|
async function populateMembershipData() {
|
||
|
try {
|
||
|
// show the memberships the user is subscribed to
|
||
|
const response = await Lbryio.call(
|
||
|
'membership',
|
||
|
'mine',
|
||
|
{
|
||
|
environment: stripeEnvironment,
|
||
|
},
|
||
|
'post'
|
||
|
);
|
||
|
|
||
|
let activeMemberships = [];
|
||
|
let canceledMemberships = [];
|
||
|
let purchasedMemberships = [];
|
||
|
|
||
|
for (const membership of response) {
|
||
|
// if it's autorenewing it's considered 'active'
|
||
|
const isActive = membership.Membership.auto_renew;
|
||
|
if (isActive) {
|
||
|
activeMemberships.push(membership);
|
||
|
} else {
|
||
|
canceledMemberships.push(membership);
|
||
|
}
|
||
|
purchasedMemberships.push(membership.Membership.membership_id);
|
||
|
}
|
||
|
|
||
|
// hide the other membership options if there's already a purchased membership
|
||
|
if (activeMemberships.length > 0) {
|
||
|
setMembershipOptions(false);
|
||
|
}
|
||
|
|
||
|
setActiveMemberships(activeMemberships);
|
||
|
setCanceledMemberships(canceledMemberships);
|
||
|
setPurchasedMemberships(purchasedMemberships);
|
||
|
|
||
|
// update the state to show the badge
|
||
|
fetchUserMemberships(userChannelClaimId || '');
|
||
|
|
||
|
setUserMemberships(response);
|
||
|
} catch (err) {
|
||
|
console.log(err);
|
||
|
}
|
||
|
setFetchUserMemberships(false);
|
||
|
}
|
||
|
|
||
|
React.useEffect(() => {
|
||
|
if (!shouldFetchUserMemberships) setFetchUserMemberships(true);
|
||
|
}, [shouldFetchUserMemberships]);
|
||
|
|
||
|
React.useEffect(function () {
|
||
|
(async function () {
|
||
|
try {
|
||
|
// check if there is a payment method
|
||
|
const response = await Lbryio.call(
|
||
|
'customer',
|
||
|
'status',
|
||
|
{
|
||
|
environment: stripeEnvironment,
|
||
|
},
|
||
|
'post'
|
||
|
);
|
||
|
// hardcoded to first card
|
||
|
const hasAPaymentCard = Boolean(response && response.PaymentMethods && response.PaymentMethods[0]);
|
||
|
|
||
|
setCardSaved(hasAPaymentCard);
|
||
|
} catch (err) {
|
||
|
const customerDoesntExistError = 'user as customer is not setup yet';
|
||
|
if (err.message === customerDoesntExistError) {
|
||
|
setCardSaved(false);
|
||
|
} else {
|
||
|
console.log(err);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
// check the available membership for odysee.com
|
||
|
const response = await Lbryio.call(
|
||
|
'membership',
|
||
|
'list',
|
||
|
{
|
||
|
environment: stripeEnvironment,
|
||
|
channel_id: odyseeChannelId,
|
||
|
channel_name: odyseeChannelName,
|
||
|
},
|
||
|
'post'
|
||
|
);
|
||
|
|
||
|
// hide other options if there's already a membership
|
||
|
if (activeMemberships && activeMemberships.length > 0) {
|
||
|
setMembershipOptions(false);
|
||
|
} else {
|
||
|
setMembershipOptions(response);
|
||
|
}
|
||
|
} catch (err) {
|
||
|
console.log(err);
|
||
|
}
|
||
|
|
||
|
populateMembershipData();
|
||
|
})();
|
||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
|
}, []);
|
||
|
|
||
|
const stillWaitingFromBackend =
|
||
|
purchasedMemberships === undefined ||
|
||
|
cardSaved === undefined ||
|
||
|
membershipOptions === undefined ||
|
||
|
userMemberships === undefined;
|
||
|
|
||
|
const formatDate = function (date) {
|
||
|
return moment(new Date(date)).format('MMMM DD YYYY');
|
||
|
};
|
||
|
|
||
|
const deleteData = async function () {
|
||
|
await Lbryio.call('membership', 'clear', {}, 'post');
|
||
|
// $FlowFixMe
|
||
|
location.reload();
|
||
|
};
|
||
|
|
||
|
// dont pass channel name and id when calling purchase
|
||
|
const noChannelsOrIncognitoMode = incognito || !channels;
|
||
|
|
||
|
// TODO: can clean this up, some repeating text
|
||
|
function buildPurchaseString(price, interval, plan) {
|
||
|
let featureString = '';
|
||
|
// generate different strings depending on other conditions
|
||
|
if (plan === 'Premium' && !noChannelsOrIncognitoMode) {
|
||
|
featureString =
|
||
|
'Your badge will be shown for your ' +
|
||
|
userChannelName +
|
||
|
' channel in all areas of the app, and can be added to two additional channels in the future for free. ';
|
||
|
} else if (plan === 'Premium+' && !noChannelsOrIncognitoMode) {
|
||
|
featureString =
|
||
|
'The no ads feature applies site-wide for all channels and your badge will be shown for your ' +
|
||
|
userChannelName +
|
||
|
' channel in all areas of the app, and can be added to two additional channels in the future for free. ';
|
||
|
} else if (plan === 'Premium' && !channels) {
|
||
|
featureString =
|
||
|
'You currently have no channels. To show your badge on a channel, please create a channel first. ' +
|
||
|
'If you register a channel later you will be able to show a badge for up to three channels.';
|
||
|
} else if (plan === 'Premium+' && !channels) {
|
||
|
featureString =
|
||
|
'The no ads feature applies site-wide. You currently have no channels. To show your badge on a channel, please create a channel first. ' +
|
||
|
'If you register a channel later you will be able to show a badge for up to three channels.';
|
||
|
} else if (plan === 'Premium' && incognito) {
|
||
|
featureString =
|
||
|
'You currently have no channel selected and will not have a badge be visible, if you want to show a badge you can select a channel now, ' +
|
||
|
'or you can show a badge for up to three channels in the future for free.';
|
||
|
} else if (plan === 'Premium+' && incognito) {
|
||
|
featureString =
|
||
|
'The no ads feature applies site-wide. You currently have no channel selected and will not have a badge be visible, ' +
|
||
|
'if you want to show a badge you can select a channel now, or you can show a badge for up to three channels in the future for free.';
|
||
|
}
|
||
|
|
||
|
let purchaseString =
|
||
|
`You are purchasing a ${interval}ly membership, that is active immediately ` +
|
||
|
`and will renew ${interval}ly at a price of ${currencyToUse.toUpperCase()} ${
|
||
|
currencyToUse === 'usd' ? '$' : '€'
|
||
|
}${price / 100}. ` +
|
||
|
featureString +
|
||
|
'You can cancel Premium at any time (no refunds) and you can also close this window and choose a different membership option.';
|
||
|
|
||
|
return __(purchaseString);
|
||
|
}
|
||
|
|
||
|
const purchaseMembership = function (e, membershipOption, price) {
|
||
|
e.preventDefault();
|
||
|
e.stopPropagation();
|
||
|
|
||
|
const planName = membershipOption.Membership.name;
|
||
|
|
||
|
const membershipId = e.currentTarget.getAttribute('membership-id');
|
||
|
const priceId = e.currentTarget.getAttribute('price-id');
|
||
|
const purchaseString = buildPurchaseString(price.unit_amount, price.recurring.interval, planName);
|
||
|
|
||
|
openModal(MODALS.CONFIRM_ODYSEE_MEMBERSHIP, {
|
||
|
membershipId,
|
||
|
userChannelClaimId: noChannelsOrIncognitoMode ? undefined : userChannelClaimId,
|
||
|
userChannelName: noChannelsOrIncognitoMode ? undefined : userChannelName,
|
||
|
priceId,
|
||
|
purchaseString,
|
||
|
plan: planName,
|
||
|
populateMembershipData,
|
||
|
setMembershipOptions,
|
||
|
updateUserOdyseeMembershipStatus,
|
||
|
user,
|
||
|
});
|
||
|
};
|
||
|
|
||
|
const cancelMembership = async function (e, membership) {
|
||
|
const membershipId = e.currentTarget.getAttribute('membership-id');
|
||
|
|
||
|
const cancellationString =
|
||
|
'You are cancelling your Odysee Premium. You will still have access to all the paid ' +
|
||
|
'features until the point of the expiration of your current membership, at which point you will not be charged ' +
|
||
|
'again and your membership will no longer be active. At this time, there is no way to subscribe to another membership if you cancel and there are no refunds.';
|
||
|
|
||
|
openModal(MODALS.CONFIRM_ODYSEE_MEMBERSHIP, {
|
||
|
membershipId,
|
||
|
hasMembership,
|
||
|
purchaseString: cancellationString,
|
||
|
populateMembershipData,
|
||
|
});
|
||
|
};
|
||
|
|
||
|
function convertPriceToString(price) {
|
||
|
const interval = price.recurring.interval;
|
||
|
|
||
|
if (interval === 'year') {
|
||
|
return 'Yearly';
|
||
|
} else if (interval === 'month') {
|
||
|
return 'Monthly';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function capitalizeWord(string) {
|
||
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||
|
}
|
||
|
|
||
|
function buildCurrencyDisplay(priceObject) {
|
||
|
let currencySymbol;
|
||
|
if (priceObject.currency === 'eur') {
|
||
|
currencySymbol = '€';
|
||
|
} else if (priceObject.currency === 'usd') {
|
||
|
currencySymbol = '$';
|
||
|
}
|
||
|
|
||
|
const currency = priceObject.currency.toUpperCase();
|
||
|
|
||
|
return currency + ' ' + currencySymbol;
|
||
|
}
|
||
|
|
||
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
||
|
const params = Object.fromEntries(urlSearchParams.entries());
|
||
|
|
||
|
const { interval, plan } = params;
|
||
|
|
||
|
const planValue = params.plan;
|
||
|
|
||
|
// description to be shown under plan name
|
||
|
function getPlanDescription(plan) {
|
||
|
if (plan === 'Premium') {
|
||
|
return 'Badge on profile, exclusive and early access to features';
|
||
|
|
||
|
// if there's more plans added this needs to be expanded
|
||
|
} else {
|
||
|
return 'All Premium features, and no ads';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// add a bit of a delay otherwise it's a bit jarring
|
||
|
const timeoutValue = 300;
|
||
|
|
||
|
// if user already selected plan, wait a bit (so it's not jarring) and open modal
|
||
|
React.useEffect(() => {
|
||
|
if (!stillWaitingFromBackend && planValue && cardSaved) {
|
||
|
setTimeout(function () {
|
||
|
// clear query params
|
||
|
window.history.replaceState(null, null, window.location.pathname);
|
||
|
|
||
|
setHasShownModal(true);
|
||
|
|
||
|
// open confirm purchase
|
||
|
// $FlowFixMe
|
||
|
document.querySelector('[plan="' + plan + '"][interval="' + interval + '"]').click();
|
||
|
}, timeoutValue);
|
||
|
}
|
||
|
}, [stillWaitingFromBackend, planValue, cardSaved]);
|
||
|
|
||
|
const helpText = (
|
||
|
<div className="section__subtitle">
|
||
|
<p>
|
||
|
{__(
|
||
|
`First of all, thank you for considering or purchasing a membership, it means a ton to us! A few important details to know: `
|
||
|
)}
|
||
|
</p>
|
||
|
<p>
|
||
|
<ul>
|
||
|
<li>
|
||
|
{__(
|
||
|
`Early access and exclusive features include: livestreaming and the ability to post odysee hyperlinks and images in comments + blogs. More to come later.`
|
||
|
)}
|
||
|
<li>
|
||
|
{__(
|
||
|
`The yearly Premium+ membership has a discount compared to monthly, and Premium is only available yearly.`
|
||
|
)}
|
||
|
</li>
|
||
|
<li>{__(`These are limited time rates, so get in early!`)}</li>
|
||
|
</li>
|
||
|
<li>
|
||
|
{__(
|
||
|
`There may be higher tiers available in the future for creators and anyone else who wants to support us.`
|
||
|
)}
|
||
|
</li>
|
||
|
<li>
|
||
|
{__(`Badges will be displayed on a single channel to start, with an option to add on two more later on.`)}
|
||
|
</li>
|
||
|
<li>
|
||
|
{__(`Cannot upgrade or downgrade a membership at this time. Refunds are not available. Choose wisely.`)}
|
||
|
</li>
|
||
|
</ul>
|
||
|
</p>
|
||
|
</div>
|
||
|
);
|
||
|
|
||
|
return (
|
||
|
<>
|
||
|
<Page className="premium-wrapper">
|
||
|
{/** splash frontend **/}
|
||
|
{!stillWaitingFromBackend && purchasedMemberships.length === 0 && !planValue && !hasShownModal ? (
|
||
|
<MembershipSplash pageLocation={'confirmPage'} currencyToUse={currencyToUse} />
|
||
|
) : (
|
||
|
/** odysee membership page **/
|
||
|
<div className={'card-stack'}>
|
||
|
{!stillWaitingFromBackend && cardSaved !== false && (
|
||
|
<>
|
||
|
<h1 style={{ fontSize: '23px' }}>{__('Odysee Premium')}</h1>
|
||
|
{/* let user switch channel */}
|
||
|
<div style={{ marginTop: '10px' }}>
|
||
|
<ChannelSelector uri={activeChannelClaim && activeChannelClaim.permanent_url} />
|
||
|
|
||
|
{/* explainer help text */}
|
||
|
<Card
|
||
|
titleActions={
|
||
|
<Button
|
||
|
button="close"
|
||
|
icon={showHelp ? ICONS.UP : ICONS.DOWN}
|
||
|
onClick={() => setShowHelp(!showHelp)}
|
||
|
/>
|
||
|
}
|
||
|
title={__('Get More Information')}
|
||
|
subtitle={<>{__(`Expand to learn more about how Odysee Premium works`)} </>}
|
||
|
actions={showHelp && helpText}
|
||
|
className={'premium-explanation-text'}
|
||
|
/>
|
||
|
</div>
|
||
|
</>
|
||
|
)}
|
||
|
|
||
|
{/** available memberships **/}
|
||
|
{/* if they have a card and don't have a membership yet */}
|
||
|
{!stillWaitingFromBackend && membershipOptions && purchasedMemberships.length < 1 && cardSaved !== false && (
|
||
|
<>
|
||
|
<div className="card__title-section">
|
||
|
<h2 className="card__title">{__('Available Memberships')}</h2>
|
||
|
</div>
|
||
|
|
||
|
<Card>
|
||
|
{membershipOptions.map((membershipOption, i) => (
|
||
|
<>
|
||
|
<div key={i}>
|
||
|
{purchasedMemberships && !purchasedMemberships.includes(membershipOption.Membership.id) && (
|
||
|
<>
|
||
|
<div className="premium-option">
|
||
|
<h4 className="membership_title">
|
||
|
{membershipOption.Membership.name}
|
||
|
<PremiumBadge membership={membershipOption.Membership.name} />
|
||
|
</h4>
|
||
|
|
||
|
{/* plan description */}
|
||
|
<h4 className="membership_subtitle">
|
||
|
{getPlanDescription(membershipOption.Membership.name)}
|
||
|
</h4>
|
||
|
<>
|
||
|
{membershipOption.Prices.map((price) => (
|
||
|
<>
|
||
|
{/* dont show a monthly Premium membership option */}
|
||
|
{!(
|
||
|
price.recurring.interval === 'month' &&
|
||
|
membershipOption.Membership.name === 'Premium'
|
||
|
) && (
|
||
|
<>
|
||
|
{price.currency === currencyToUse && (
|
||
|
<div>
|
||
|
<h4 className="membership_info">
|
||
|
<b>Interval:</b> {convertPriceToString(price)}
|
||
|
</h4>
|
||
|
<h4 className="membership_info">
|
||
|
<b>Price:</b> {buildCurrencyDisplay(price)}
|
||
|
{price.unit_amount / 100}/{capitalizeWord(price.recurring.interval)}
|
||
|
</h4>
|
||
|
<Button
|
||
|
button="primary"
|
||
|
onClick={(e) => purchaseMembership(e, membershipOption, price)}
|
||
|
membership-id={membershipOption.Membership.id}
|
||
|
membership-subscription-period={membershipOption.Membership.type}
|
||
|
price-id={price.id}
|
||
|
className="membership_button"
|
||
|
label={__('Join via ' + price.recurring.interval + 'ly membership')}
|
||
|
icon={ICONS.FINANCE}
|
||
|
interval={price.recurring.interval}
|
||
|
plan={membershipOption.Membership.name}
|
||
|
/>
|
||
|
</div>
|
||
|
)}
|
||
|
</>
|
||
|
)}
|
||
|
</>
|
||
|
))}
|
||
|
</>
|
||
|
</div>
|
||
|
</>
|
||
|
)}
|
||
|
</div>
|
||
|
</>
|
||
|
))}
|
||
|
</Card>
|
||
|
</>
|
||
|
)}
|
||
|
{!stillWaitingFromBackend && cardSaved === true && (
|
||
|
<>
|
||
|
<div className="card__title-section">
|
||
|
<h2 className="card__title">{__('Your Active Memberships')}</h2>
|
||
|
</div>
|
||
|
|
||
|
<Card>
|
||
|
{/** * list of active memberships from user ***/}
|
||
|
<div>
|
||
|
{/* <h1 style={{ fontSize: '19px' }}>Active Memberships</h1> */}
|
||
|
{!stillWaitingFromBackend && activeMemberships && activeMemberships.length === 0 && (
|
||
|
<>
|
||
|
<h4>{__('You currently have no active memberships')}</h4>
|
||
|
</>
|
||
|
)}
|
||
|
{/** active memberships **/}
|
||
|
{!stillWaitingFromBackend &&
|
||
|
activeMemberships &&
|
||
|
activeMemberships.map((membership) => (
|
||
|
<>
|
||
|
<div className="premium-option">
|
||
|
{/* membership name */}
|
||
|
<h4 className="membership_title">
|
||
|
{membership.MembershipDetails.name}
|
||
|
<PremiumBadge membership={membership.MembershipDetails.name} />
|
||
|
</h4>
|
||
|
|
||
|
{/* description section */}
|
||
|
<h4 className="membership_subtitle">
|
||
|
{getPlanDescription(membership.MembershipDetails.name)}
|
||
|
</h4>
|
||
|
|
||
|
<h4 className="membership_info">
|
||
|
<b>{__('Registered On:')}</b> {formatDate(membership.Membership.created_at)}
|
||
|
</h4>
|
||
|
<h4 className="membership_info">
|
||
|
<b>{__('Auto-Renews On')}:</b>{' '}
|
||
|
{formatDate(membership.Subscription.current_period_end * 1000)}
|
||
|
</h4>
|
||
|
{!stillWaitingFromBackend && membership.type === 'yearly' && (
|
||
|
<>
|
||
|
<h4 className="membership_info">
|
||
|
<b>{__('Membership Period Options:')}</b> {__('Yearly')}
|
||
|
</h4>
|
||
|
<h4 className="membership_info">
|
||
|
${(membership.cost_usd * 12) / 100} {__('USD For A One Year Membership')} ($
|
||
|
{membership.cost_usd / 100} {__('Per Month')})
|
||
|
</h4>
|
||
|
</>
|
||
|
)}
|
||
|
<Button
|
||
|
button="alt"
|
||
|
membership-id={membership.Membership.membership_id}
|
||
|
onClick={(e) => cancelMembership(e, membership)}
|
||
|
className="cancel-membership-button"
|
||
|
label={__('Cancel membership')}
|
||
|
icon={ICONS.FINANCE}
|
||
|
/>
|
||
|
</div>
|
||
|
</>
|
||
|
))}
|
||
|
</div>
|
||
|
</Card>
|
||
|
<>
|
||
|
{/** canceled memberships **/}
|
||
|
<div className="card__title-section">
|
||
|
<h2 className="card__title">{__('Canceled Memberships')}</h2>
|
||
|
</div>
|
||
|
<Card>
|
||
|
{canceledMemberships && canceledMemberships.length === 0 && (
|
||
|
<>
|
||
|
<h4>{__('You currently have no canceled memberships')}</h4>
|
||
|
</>
|
||
|
)}
|
||
|
{canceledMemberships &&
|
||
|
canceledMemberships.map((membership) => (
|
||
|
<>
|
||
|
<h4 className="membership_title">
|
||
|
{membership.MembershipDetails.name}
|
||
|
<PremiumBadge membership={membership.MembershipDetails.name} />
|
||
|
</h4>
|
||
|
|
||
|
<div className="premium-option">
|
||
|
<h4 className="membership_info">
|
||
|
<b>{__('Registered On:')}</b> {formatDate(membership.Membership.created_at)}
|
||
|
</h4>
|
||
|
<h4 className="membership_info">
|
||
|
<b>{__('Canceled On:')}</b> {formatDate(membership.Subscription.canceled_at * 1000)}
|
||
|
</h4>
|
||
|
<h4 className="membership_info">
|
||
|
<b>{__('Still Valid Until:')}</b> {formatDate(membership.Membership.expires)}
|
||
|
</h4>
|
||
|
</div>
|
||
|
</>
|
||
|
))}
|
||
|
</Card>
|
||
|
</>
|
||
|
</>
|
||
|
)}
|
||
|
|
||
|
{/** send user to add card if they don't have one yet */}
|
||
|
{!stillWaitingFromBackend && cardSaved === false && (
|
||
|
<div>
|
||
|
<br />
|
||
|
<h2 className={'getPaymentCard'}>
|
||
|
{__(
|
||
|
'Please save a card as a payment method so you can join Odysee Premium. After the card is added, click Back.'
|
||
|
)}
|
||
|
</h2>
|
||
|
|
||
|
<Button
|
||
|
button="primary"
|
||
|
label={__('Add A Card')}
|
||
|
icon={ICONS.SETTINGS}
|
||
|
navigate={`/$/${PAGES.SETTINGS_STRIPE_CARD}`}
|
||
|
className="membership_button"
|
||
|
/>
|
||
|
</div>
|
||
|
)}
|
||
|
|
||
|
{/** loading section **/}
|
||
|
{stillWaitingFromBackend && (
|
||
|
<div className="main--empty">
|
||
|
<Spinner />
|
||
|
</div>
|
||
|
)}
|
||
|
|
||
|
{/** clear membership data (only available on dev) **/}
|
||
|
{isDev && cardSaved && purchasedMemberships.length > 0 && (
|
||
|
<>
|
||
|
<h1 style={{ marginTop: '30px', fontSize: '20px' }}>
|
||
|
{__('Clear Membership Data (Only Available On Dev)')}
|
||
|
</h1>
|
||
|
<div>
|
||
|
<Button
|
||
|
button="primary"
|
||
|
label={__('Clear Membership Data')}
|
||
|
icon={ICONS.SETTINGS}
|
||
|
className="membership_button"
|
||
|
onClick={deleteData}
|
||
|
/>
|
||
|
</div>
|
||
|
</>
|
||
|
)}
|
||
|
</div>
|
||
|
)}
|
||
|
</Page>
|
||
|
</>
|
||
|
);
|
||
|
};
|
||
|
|
||
|
export default OdyseeMembershipPage;
|