update plan selector to use mui dialog
This commit is contained in:
parent
03228664f6
commit
1abd62964f
|
@ -38,10 +38,7 @@ const LinkButton: FC<LinkProps<'button', { color?: ButtonProps['color'] }>> = ({
|
||||||
<Link
|
<Link
|
||||||
component="button"
|
component="button"
|
||||||
sx={{
|
sx={{
|
||||||
color:
|
color: props.color && `${props.color}.main`,
|
||||||
props.color && typeof props.color === 'object'
|
|
||||||
? `${props.color}.main`
|
|
||||||
: props.color,
|
|
||||||
...sx,
|
...sx,
|
||||||
}}
|
}}
|
||||||
{...props}>
|
{...props}>
|
||||||
|
|
|
@ -1,417 +0,0 @@
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
|
||||||
import { Form, Modal, Button } from 'react-bootstrap';
|
|
||||||
import constants from 'utils/strings/constants';
|
|
||||||
import styled, { css } from 'styled-components';
|
|
||||||
import { Plan, Subscription } from 'types/billing';
|
|
||||||
import {
|
|
||||||
convertBytesToGBs,
|
|
||||||
getUserSubscription,
|
|
||||||
isUserSubscribedPlan,
|
|
||||||
isSubscriptionCancelled,
|
|
||||||
updatePaymentMethod,
|
|
||||||
updateSubscription,
|
|
||||||
activateSubscription,
|
|
||||||
cancelSubscription,
|
|
||||||
hasStripeSubscription,
|
|
||||||
hasPaidSubscription,
|
|
||||||
isOnFreePlan,
|
|
||||||
planForSubscription,
|
|
||||||
hasMobileSubscription,
|
|
||||||
hasPaypalSubscription,
|
|
||||||
manageFamilyMethod,
|
|
||||||
} from 'utils/billing';
|
|
||||||
import { reverseString } from 'utils/common';
|
|
||||||
import ArrowEast from 'components/icons/ArrowEast';
|
|
||||||
import LinkButton from './LinkButton';
|
|
||||||
import { DeadCenter, GalleryContext } from 'pages/gallery';
|
|
||||||
import billingService from 'services/billingService';
|
|
||||||
import { SetLoading } from 'types/gallery';
|
|
||||||
import { logError } from 'utils/sentry';
|
|
||||||
import { AppContext } from 'pages/_app';
|
|
||||||
|
|
||||||
export const PlanIcon = styled.div<{ currentlySubscribed: boolean }>`
|
|
||||||
border-radius: 20px;
|
|
||||||
width: 220px;
|
|
||||||
border: 2px solid #333;
|
|
||||||
padding: 30px;
|
|
||||||
margin: 10px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 20px;
|
|
||||||
background-color: #ffffff00;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
cursor: ${(props) =>
|
|
||||||
props.currentlySubscribed ? 'not-allowed' : 'pointer'};
|
|
||||||
border-color: ${(props) => props.currentlySubscribed && '#56e066'};
|
|
||||||
transition: all 0.3s ease-out;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
& > div:first-child::before {
|
|
||||||
content: ' ';
|
|
||||||
height: 600px;
|
|
||||||
width: 50px;
|
|
||||||
background-color: #444;
|
|
||||||
left: 0;
|
|
||||||
top: -50%;
|
|
||||||
position: absolute;
|
|
||||||
transform: rotate(45deg) translateX(-200px);
|
|
||||||
transition: all 0.5s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
${(props) =>
|
|
||||||
!props.currentlySubscribed &&
|
|
||||||
css`
|
|
||||||
{
|
|
||||||
transform: scale(1.1);
|
|
||||||
background-color: #ffffff11;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
&:hover
|
|
||||||
> div:first-child::before {
|
|
||||||
transform: rotate(45deg) translateX(300px);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
modalView: boolean;
|
|
||||||
closeModal: any;
|
|
||||||
|
|
||||||
setLoading: SetLoading;
|
|
||||||
}
|
|
||||||
enum PLAN_PERIOD {
|
|
||||||
MONTH = 'month',
|
|
||||||
YEAR = 'year',
|
|
||||||
}
|
|
||||||
function PlanSelector(props: Props) {
|
|
||||||
const subscription: Subscription = getUserSubscription();
|
|
||||||
const [plans, setPlans] = useState<Plan[]>(null);
|
|
||||||
const [planPeriod, setPlanPeriod] = useState<PLAN_PERIOD>(PLAN_PERIOD.YEAR);
|
|
||||||
const galleryContext = useContext(GalleryContext);
|
|
||||||
const appContext = useContext(AppContext);
|
|
||||||
|
|
||||||
const togglePeriod = () => {
|
|
||||||
setPlanPeriod((prevPeriod) =>
|
|
||||||
prevPeriod === PLAN_PERIOD.MONTH
|
|
||||||
? PLAN_PERIOD.YEAR
|
|
||||||
: PLAN_PERIOD.MONTH
|
|
||||||
);
|
|
||||||
};
|
|
||||||
function onReopenClick() {
|
|
||||||
appContext.closeMessageDialog();
|
|
||||||
galleryContext.showPlanSelectorModal();
|
|
||||||
}
|
|
||||||
useEffect(() => {
|
|
||||||
if (!props.modalView) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const main = async () => {
|
|
||||||
try {
|
|
||||||
props.setLoading(true);
|
|
||||||
let plans = await billingService.getPlans();
|
|
||||||
|
|
||||||
const planNotListed =
|
|
||||||
plans.filter((plan) =>
|
|
||||||
isUserSubscribedPlan(plan, subscription)
|
|
||||||
).length === 0;
|
|
||||||
if (
|
|
||||||
subscription &&
|
|
||||||
!isOnFreePlan(subscription) &&
|
|
||||||
planNotListed
|
|
||||||
) {
|
|
||||||
plans = [planForSubscription(subscription), ...plans];
|
|
||||||
}
|
|
||||||
setPlans(plans);
|
|
||||||
} catch (e) {
|
|
||||||
logError(e, 'plan selector modal open failed');
|
|
||||||
props.closeModal();
|
|
||||||
appContext.setDialogMessage({
|
|
||||||
title: constants.OPEN_PLAN_SELECTOR_MODAL_FAILED,
|
|
||||||
content: constants.UNKNOWN_ERROR,
|
|
||||||
close: { text: 'close', variant: 'danger' },
|
|
||||||
proceed: {
|
|
||||||
text: constants.REOPEN_PLAN_SELECTOR_MODAL,
|
|
||||||
variant: 'success',
|
|
||||||
action: onReopenClick,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
props.setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
main();
|
|
||||||
}, [props.modalView]);
|
|
||||||
|
|
||||||
async function onPlanSelect(plan: Plan) {
|
|
||||||
if (
|
|
||||||
hasMobileSubscription(subscription) &&
|
|
||||||
!isSubscriptionCancelled(subscription)
|
|
||||||
) {
|
|
||||||
appContext.setDialogMessage({
|
|
||||||
title: constants.ERROR,
|
|
||||||
content: constants.CANCEL_SUBSCRIPTION_ON_MOBILE,
|
|
||||||
close: { variant: 'danger' },
|
|
||||||
});
|
|
||||||
} else if (
|
|
||||||
hasPaypalSubscription(subscription) &&
|
|
||||||
!isSubscriptionCancelled(subscription)
|
|
||||||
) {
|
|
||||||
appContext.setDialogMessage({
|
|
||||||
title: constants.MANAGE_PLAN,
|
|
||||||
content: constants.PAYPAL_MANAGE_NOT_SUPPORTED_MESSAGE(),
|
|
||||||
close: { variant: 'danger' },
|
|
||||||
});
|
|
||||||
} else if (hasStripeSubscription(subscription)) {
|
|
||||||
appContext.setDialogMessage({
|
|
||||||
title: `${constants.CONFIRM} ${reverseString(
|
|
||||||
constants.UPDATE_SUBSCRIPTION
|
|
||||||
)}`,
|
|
||||||
content: constants.UPDATE_SUBSCRIPTION_MESSAGE,
|
|
||||||
proceed: {
|
|
||||||
text: constants.UPDATE_SUBSCRIPTION,
|
|
||||||
action: updateSubscription.bind(
|
|
||||||
null,
|
|
||||||
plan,
|
|
||||||
appContext.setDialogMessage,
|
|
||||||
props.setLoading,
|
|
||||||
props.closeModal
|
|
||||||
),
|
|
||||||
variant: 'success',
|
|
||||||
},
|
|
||||||
close: { text: constants.CANCEL },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
props.setLoading(true);
|
|
||||||
await billingService.buySubscription(plan.stripeID);
|
|
||||||
} catch (e) {
|
|
||||||
props.setLoading(false);
|
|
||||||
appContext.setDialogMessage({
|
|
||||||
title: constants.ERROR,
|
|
||||||
content: constants.SUBSCRIPTION_PURCHASE_FAILED,
|
|
||||||
close: { variant: 'danger' },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PlanIcons: JSX.Element[] = plans
|
|
||||||
?.filter((plan) => plan.period === planPeriod)
|
|
||||||
?.map((plan) => (
|
|
||||||
<PlanIcon
|
|
||||||
key={plan.stripeID}
|
|
||||||
className="subscription-plan-selector"
|
|
||||||
currentlySubscribed={isUserSubscribedPlan(plan, subscription)}
|
|
||||||
onClick={
|
|
||||||
isUserSubscribedPlan(plan, subscription)
|
|
||||||
? () => {}
|
|
||||||
: async () => await onPlanSelect(plan)
|
|
||||||
}>
|
|
||||||
<div>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: '#ECECEC',
|
|
||||||
fontWeight: 900,
|
|
||||||
fontSize: '40px',
|
|
||||||
lineHeight: '40px',
|
|
||||||
}}>
|
|
||||||
{convertBytesToGBs(plan.storage, 0)}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: '#858585',
|
|
||||||
fontSize: '24px',
|
|
||||||
fontWeight: 900,
|
|
||||||
}}>
|
|
||||||
{' '}
|
|
||||||
GB
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="bold-text"
|
|
||||||
style={{
|
|
||||||
color: '#aaa',
|
|
||||||
lineHeight: '36px',
|
|
||||||
fontSize: '20px',
|
|
||||||
}}>
|
|
||||||
{`${plan.price} / ${plan.period}`}
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="outline-success"
|
|
||||||
block
|
|
||||||
style={{
|
|
||||||
marginTop: '20px',
|
|
||||||
fontSize: '14px',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
disabled={isUserSubscribedPlan(plan, subscription)}>
|
|
||||||
{constants.CHOOSE_PLAN_BTN}
|
|
||||||
<ArrowEast style={{ marginLeft: '5px' }} />
|
|
||||||
</Button>
|
|
||||||
</PlanIcon>
|
|
||||||
));
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
show={props.modalView}
|
|
||||||
onHide={props.closeModal}
|
|
||||||
size="xl"
|
|
||||||
centered
|
|
||||||
backdrop={hasPaidSubscription(subscription) ? true : 'static'}
|
|
||||||
contentClassName="plan-selector-modal-content">
|
|
||||||
<Modal.Header closeButton>
|
|
||||||
<Modal.Title
|
|
||||||
style={{
|
|
||||||
marginLeft: '12px',
|
|
||||||
width: '100%',
|
|
||||||
textAlign: 'center',
|
|
||||||
}}>
|
|
||||||
<span>
|
|
||||||
{hasPaidSubscription(subscription)
|
|
||||||
? constants.MANAGE_PLAN
|
|
||||||
: constants.CHOOSE_PLAN}
|
|
||||||
</span>
|
|
||||||
</Modal.Title>
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Body style={{ marginTop: '20px' }}>
|
|
||||||
<DeadCenter>
|
|
||||||
<div style={{ display: 'flex' }}>
|
|
||||||
<span
|
|
||||||
className="bold-text"
|
|
||||||
style={{ fontSize: '16px' }}>
|
|
||||||
{constants.MONTHLY}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<Form.Switch
|
|
||||||
checked={planPeriod === PLAN_PERIOD.YEAR}
|
|
||||||
id="plan-period-toggler"
|
|
||||||
style={{
|
|
||||||
margin: '-4px 0 20px 15px',
|
|
||||||
fontSize: '10px',
|
|
||||||
}}
|
|
||||||
className="custom-switch-md"
|
|
||||||
onChange={togglePeriod}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className="bold-text"
|
|
||||||
style={{ fontSize: '16px' }}>
|
|
||||||
{constants.YEARLY}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</DeadCenter>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-around',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
minHeight: '212px',
|
|
||||||
margin: '5px 0',
|
|
||||||
}}>
|
|
||||||
{plans && PlanIcons}
|
|
||||||
</div>
|
|
||||||
<DeadCenter style={{ marginBottom: '30px' }}>
|
|
||||||
{hasPaidSubscription(subscription) ? (
|
|
||||||
<>
|
|
||||||
{hasStripeSubscription(subscription) && (
|
|
||||||
<>
|
|
||||||
{isSubscriptionCancelled(subscription) ? (
|
|
||||||
<LinkButton
|
|
||||||
color="success"
|
|
||||||
onClick={() =>
|
|
||||||
appContext.setDialogMessage({
|
|
||||||
title: constants.CONFIRM_ACTIVATE_SUBSCRIPTION,
|
|
||||||
content:
|
|
||||||
constants.ACTIVATE_SUBSCRIPTION_MESSAGE(
|
|
||||||
subscription.expiryTime
|
|
||||||
),
|
|
||||||
|
|
||||||
proceed: {
|
|
||||||
text: constants.ACTIVATE_SUBSCRIPTION,
|
|
||||||
action: activateSubscription.bind(
|
|
||||||
null,
|
|
||||||
appContext.setDialogMessage,
|
|
||||||
props.closeModal,
|
|
||||||
props.setLoading
|
|
||||||
),
|
|
||||||
variant: 'success',
|
|
||||||
},
|
|
||||||
close: {
|
|
||||||
text: constants.CANCEL,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
{constants.ACTIVATE_SUBSCRIPTION}
|
|
||||||
</LinkButton>
|
|
||||||
) : (
|
|
||||||
<LinkButton
|
|
||||||
color="danger"
|
|
||||||
onClick={() =>
|
|
||||||
appContext.setDialogMessage({
|
|
||||||
title: constants.CONFIRM_CANCEL_SUBSCRIPTION,
|
|
||||||
content:
|
|
||||||
constants.CANCEL_SUBSCRIPTION_MESSAGE(),
|
|
||||||
|
|
||||||
proceed: {
|
|
||||||
text: constants.CANCEL_SUBSCRIPTION,
|
|
||||||
action: cancelSubscription.bind(
|
|
||||||
null,
|
|
||||||
appContext.setDialogMessage,
|
|
||||||
props.closeModal,
|
|
||||||
props.setLoading
|
|
||||||
),
|
|
||||||
variant: 'danger',
|
|
||||||
},
|
|
||||||
close: {
|
|
||||||
text: constants.CANCEL,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
{constants.CANCEL_SUBSCRIPTION}
|
|
||||||
</LinkButton>
|
|
||||||
)}
|
|
||||||
<LinkButton
|
|
||||||
color="primary"
|
|
||||||
onClick={updatePaymentMethod.bind(
|
|
||||||
null,
|
|
||||||
appContext.setDialogMessage,
|
|
||||||
props.setLoading
|
|
||||||
)}
|
|
||||||
style={{ marginTop: '20px' }}>
|
|
||||||
{constants.MANAGEMENT_PORTAL}
|
|
||||||
</LinkButton>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<LinkButton
|
|
||||||
color="primary"
|
|
||||||
onClick={manageFamilyMethod.bind(
|
|
||||||
null,
|
|
||||||
appContext.setDialogMessage,
|
|
||||||
props.setLoading
|
|
||||||
)}
|
|
||||||
style={{ marginTop: '20px' }}>
|
|
||||||
{constants.MANAGE_FAMILY_PORTAL}
|
|
||||||
</LinkButton>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<LinkButton
|
|
||||||
color="primary"
|
|
||||||
onClick={props.closeModal}
|
|
||||||
style={{
|
|
||||||
color: 'rgb(121, 121, 121)',
|
|
||||||
marginTop: '20px',
|
|
||||||
}}>
|
|
||||||
{isOnFreePlan(subscription)
|
|
||||||
? constants.SKIP
|
|
||||||
: constants.CLOSE}
|
|
||||||
</LinkButton>
|
|
||||||
)}
|
|
||||||
</DeadCenter>
|
|
||||||
</Modal.Body>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PlanSelector;
|
|
184
src/components/pages/gallery/PlanSelector/index.tsx
Normal file
184
src/components/pages/gallery/PlanSelector/index.tsx
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
import { PeriodToggler } from './periodToggler';
|
||||||
|
import { ManageSubscription } from './manageSubscription';
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import constants from 'utils/strings/constants';
|
||||||
|
import { Plan, Subscription } from 'types/billing';
|
||||||
|
import {
|
||||||
|
getUserSubscription,
|
||||||
|
isUserSubscribedPlan,
|
||||||
|
isSubscriptionCancelled,
|
||||||
|
updateSubscription,
|
||||||
|
hasStripeSubscription,
|
||||||
|
hasPaidSubscription,
|
||||||
|
isOnFreePlan,
|
||||||
|
planForSubscription,
|
||||||
|
hasMobileSubscription,
|
||||||
|
hasPaypalSubscription,
|
||||||
|
} from 'utils/billing';
|
||||||
|
import { reverseString } from 'utils/common';
|
||||||
|
import { GalleryContext } from 'pages/gallery';
|
||||||
|
import billingService from 'services/billingService';
|
||||||
|
import { SetLoading } from 'types/gallery';
|
||||||
|
import { logError } from 'utils/sentry';
|
||||||
|
import { AppContext } from 'pages/_app';
|
||||||
|
import DialogBox from 'components/DialogBox';
|
||||||
|
import Plans from './plans';
|
||||||
|
import { DialogBoxAttributes } from 'types/dialogBox';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modalView: boolean;
|
||||||
|
closeModal: any;
|
||||||
|
|
||||||
|
setLoading: SetLoading;
|
||||||
|
}
|
||||||
|
export enum PLAN_PERIOD {
|
||||||
|
MONTH = 'month',
|
||||||
|
YEAR = 'year',
|
||||||
|
}
|
||||||
|
function PlanSelector(props: Props) {
|
||||||
|
const subscription: Subscription = getUserSubscription();
|
||||||
|
const [plans, setPlans] = useState<Plan[]>(null);
|
||||||
|
const [planPeriod, setPlanPeriod] = useState<PLAN_PERIOD>(PLAN_PERIOD.YEAR);
|
||||||
|
const galleryContext = useContext(GalleryContext);
|
||||||
|
const appContext = useContext(AppContext);
|
||||||
|
|
||||||
|
const togglePeriod = () => {
|
||||||
|
setPlanPeriod((prevPeriod) =>
|
||||||
|
prevPeriod === PLAN_PERIOD.MONTH
|
||||||
|
? PLAN_PERIOD.YEAR
|
||||||
|
: PLAN_PERIOD.MONTH
|
||||||
|
);
|
||||||
|
};
|
||||||
|
function onReopenClick() {
|
||||||
|
appContext.closeMessageDialog();
|
||||||
|
galleryContext.showPlanSelectorModal();
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
if (!props.modalView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const main = async () => {
|
||||||
|
try {
|
||||||
|
props.setLoading(true);
|
||||||
|
let plans = await billingService.getPlans();
|
||||||
|
|
||||||
|
const planNotListed =
|
||||||
|
plans.filter((plan) =>
|
||||||
|
isUserSubscribedPlan(plan, subscription)
|
||||||
|
).length === 0;
|
||||||
|
if (
|
||||||
|
subscription &&
|
||||||
|
!isOnFreePlan(subscription) &&
|
||||||
|
planNotListed
|
||||||
|
) {
|
||||||
|
plans = [planForSubscription(subscription), ...plans];
|
||||||
|
}
|
||||||
|
setPlans(plans);
|
||||||
|
} catch (e) {
|
||||||
|
logError(e, 'plan selector modal open failed');
|
||||||
|
props.closeModal();
|
||||||
|
appContext.setDialogMessage({
|
||||||
|
title: constants.OPEN_PLAN_SELECTOR_MODAL_FAILED,
|
||||||
|
content: constants.UNKNOWN_ERROR,
|
||||||
|
close: { text: 'close', variant: 'danger' },
|
||||||
|
proceed: {
|
||||||
|
text: constants.REOPEN_PLAN_SELECTOR_MODAL,
|
||||||
|
variant: 'success',
|
||||||
|
action: onReopenClick,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
props.setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
main();
|
||||||
|
}, [props.modalView]);
|
||||||
|
|
||||||
|
async function onPlanSelect(plan: Plan) {
|
||||||
|
if (
|
||||||
|
hasMobileSubscription(subscription) &&
|
||||||
|
!isSubscriptionCancelled(subscription)
|
||||||
|
) {
|
||||||
|
appContext.setDialogMessage({
|
||||||
|
title: constants.ERROR,
|
||||||
|
content: constants.CANCEL_SUBSCRIPTION_ON_MOBILE,
|
||||||
|
close: { variant: 'danger' },
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
hasPaypalSubscription(subscription) &&
|
||||||
|
!isSubscriptionCancelled(subscription)
|
||||||
|
) {
|
||||||
|
appContext.setDialogMessage({
|
||||||
|
title: constants.MANAGE_PLAN,
|
||||||
|
content: constants.PAYPAL_MANAGE_NOT_SUPPORTED_MESSAGE(),
|
||||||
|
close: { variant: 'danger' },
|
||||||
|
});
|
||||||
|
} else if (hasStripeSubscription(subscription)) {
|
||||||
|
appContext.setDialogMessage({
|
||||||
|
title: `${constants.CONFIRM} ${reverseString(
|
||||||
|
constants.UPDATE_SUBSCRIPTION
|
||||||
|
)}`,
|
||||||
|
content: constants.UPDATE_SUBSCRIPTION_MESSAGE,
|
||||||
|
proceed: {
|
||||||
|
text: constants.UPDATE_SUBSCRIPTION,
|
||||||
|
action: updateSubscription.bind(
|
||||||
|
null,
|
||||||
|
plan,
|
||||||
|
appContext.setDialogMessage,
|
||||||
|
props.setLoading,
|
||||||
|
props.closeModal
|
||||||
|
),
|
||||||
|
variant: 'success',
|
||||||
|
},
|
||||||
|
close: { text: constants.CANCEL },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
props.setLoading(true);
|
||||||
|
await billingService.buySubscription(plan.stripeID);
|
||||||
|
} catch (e) {
|
||||||
|
props.setLoading(false);
|
||||||
|
appContext.setDialogMessage({
|
||||||
|
title: constants.ERROR,
|
||||||
|
content: constants.SUBSCRIPTION_PURCHASE_FAILED,
|
||||||
|
close: { variant: 'danger' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const planSelectorAttributes: DialogBoxAttributes = {
|
||||||
|
closeOnBackdropClick: hasPaidSubscription(subscription) ? true : false,
|
||||||
|
title: hasPaidSubscription(subscription)
|
||||||
|
? constants.MANAGE_PLAN
|
||||||
|
: constants.CHOOSE_PLAN,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogBox
|
||||||
|
open={props.modalView}
|
||||||
|
titleCloseButton
|
||||||
|
onClose={props.closeModal}
|
||||||
|
size={'xl'}
|
||||||
|
attributes={planSelectorAttributes}
|
||||||
|
fullWidth={false}>
|
||||||
|
<PeriodToggler
|
||||||
|
planPeriod={planPeriod}
|
||||||
|
togglePeriod={togglePeriod}
|
||||||
|
/>
|
||||||
|
<Plans
|
||||||
|
plans={plans}
|
||||||
|
planPeriod={planPeriod}
|
||||||
|
onPlanSelect={onPlanSelect}
|
||||||
|
subscription={subscription}
|
||||||
|
/>
|
||||||
|
<ManageSubscription
|
||||||
|
subscription={subscription}
|
||||||
|
closeModal={props.closeModal}
|
||||||
|
setLoading={props.setLoading}
|
||||||
|
/>
|
||||||
|
</DialogBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PlanSelector;
|
122
src/components/pages/gallery/PlanSelector/manageSubscription.tsx
Normal file
122
src/components/pages/gallery/PlanSelector/manageSubscription.tsx
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
import { DeadCenter } from 'pages/gallery';
|
||||||
|
import { AppContext } from 'pages/_app';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import {
|
||||||
|
activateSubscription,
|
||||||
|
cancelSubscription,
|
||||||
|
updatePaymentMethod,
|
||||||
|
manageFamilyMethod,
|
||||||
|
hasPaidSubscription,
|
||||||
|
hasStripeSubscription,
|
||||||
|
isOnFreePlan,
|
||||||
|
isSubscriptionCancelled,
|
||||||
|
} from 'utils/billing';
|
||||||
|
import constants from 'utils/strings/constants';
|
||||||
|
import LinkButton from '../LinkButton';
|
||||||
|
export function ManageSubscription({ subscription, ...props }) {
|
||||||
|
const appContext = useContext(AppContext);
|
||||||
|
return (
|
||||||
|
<DeadCenter
|
||||||
|
style={{
|
||||||
|
marginBottom: '30px',
|
||||||
|
}}>
|
||||||
|
{hasPaidSubscription(subscription) ? (
|
||||||
|
<>
|
||||||
|
{hasStripeSubscription(subscription) && (
|
||||||
|
<>
|
||||||
|
{isSubscriptionCancelled(subscription) ? (
|
||||||
|
<LinkButton
|
||||||
|
color="accent"
|
||||||
|
onClick={() =>
|
||||||
|
appContext.setDialogMessage({
|
||||||
|
title: constants.CONFIRM_ACTIVATE_SUBSCRIPTION,
|
||||||
|
content:
|
||||||
|
constants.ACTIVATE_SUBSCRIPTION_MESSAGE(
|
||||||
|
subscription.expiryTime
|
||||||
|
),
|
||||||
|
proceed: {
|
||||||
|
text: constants.ACTIVATE_SUBSCRIPTION,
|
||||||
|
action: activateSubscription.bind(
|
||||||
|
null,
|
||||||
|
appContext.setDialogMessage,
|
||||||
|
props.closeModal,
|
||||||
|
props.setLoading
|
||||||
|
),
|
||||||
|
variant: 'success',
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
text: constants.CANCEL,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
{constants.ACTIVATE_SUBSCRIPTION}
|
||||||
|
</LinkButton>
|
||||||
|
) : (
|
||||||
|
<LinkButton
|
||||||
|
color="danger"
|
||||||
|
onClick={() =>
|
||||||
|
appContext.setDialogMessage({
|
||||||
|
title: constants.CONFIRM_CANCEL_SUBSCRIPTION,
|
||||||
|
content:
|
||||||
|
constants.CANCEL_SUBSCRIPTION_MESSAGE(),
|
||||||
|
proceed: {
|
||||||
|
text: constants.CANCEL_SUBSCRIPTION,
|
||||||
|
action: cancelSubscription.bind(
|
||||||
|
null,
|
||||||
|
appContext.setDialogMessage,
|
||||||
|
props.closeModal,
|
||||||
|
props.setLoading
|
||||||
|
),
|
||||||
|
variant: 'danger',
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
text: constants.CANCEL,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}>
|
||||||
|
{constants.CANCEL_SUBSCRIPTION}
|
||||||
|
</LinkButton>
|
||||||
|
)}
|
||||||
|
<LinkButton
|
||||||
|
color="primary"
|
||||||
|
onClick={updatePaymentMethod.bind(
|
||||||
|
null,
|
||||||
|
appContext.setDialogMessage,
|
||||||
|
props.setLoading
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
marginTop: '20px',
|
||||||
|
}}>
|
||||||
|
{constants.MANAGEMENT_PORTAL}
|
||||||
|
</LinkButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<LinkButton
|
||||||
|
color="primary"
|
||||||
|
onClick={manageFamilyMethod.bind(
|
||||||
|
null,
|
||||||
|
appContext.setDialogMessage,
|
||||||
|
props.setLoading
|
||||||
|
)}
|
||||||
|
style={{
|
||||||
|
marginTop: '20px',
|
||||||
|
}}>
|
||||||
|
{constants.MANAGE_FAMILY_PORTAL}
|
||||||
|
</LinkButton>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<LinkButton
|
||||||
|
color="primary"
|
||||||
|
onClick={props.closeModal}
|
||||||
|
style={{
|
||||||
|
color: 'rgb(121, 121, 121)',
|
||||||
|
marginTop: '20px',
|
||||||
|
}}>
|
||||||
|
{isOnFreePlan(subscription)
|
||||||
|
? constants.SKIP
|
||||||
|
: constants.CLOSE}
|
||||||
|
</LinkButton>
|
||||||
|
)}
|
||||||
|
</DeadCenter>
|
||||||
|
);
|
||||||
|
}
|
41
src/components/pages/gallery/PlanSelector/periodToggler.tsx
Normal file
41
src/components/pages/gallery/PlanSelector/periodToggler.tsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { DeadCenter } from 'pages/gallery';
|
||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'react-bootstrap';
|
||||||
|
import constants from 'utils/strings/constants';
|
||||||
|
import { PLAN_PERIOD } from '.';
|
||||||
|
export function PeriodToggler({ planPeriod, togglePeriod }) {
|
||||||
|
return (
|
||||||
|
<DeadCenter>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
}}>
|
||||||
|
<span
|
||||||
|
className="bold-text"
|
||||||
|
style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
}}>
|
||||||
|
{constants.MONTHLY}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Form.Switch
|
||||||
|
checked={planPeriod === PLAN_PERIOD.YEAR}
|
||||||
|
id="plan-period-toggler"
|
||||||
|
style={{
|
||||||
|
margin: '-4px 0 20px 15px',
|
||||||
|
fontSize: '10px',
|
||||||
|
}}
|
||||||
|
className="custom-switch-md"
|
||||||
|
onChange={togglePeriod}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className="bold-text"
|
||||||
|
style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
}}>
|
||||||
|
{constants.YEARLY}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</DeadCenter>
|
||||||
|
);
|
||||||
|
}
|
29
src/components/pages/gallery/PlanSelector/plans/index.tsx
Normal file
29
src/components/pages/gallery/PlanSelector/plans/index.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { isUserSubscribedPlan, convertBytesToGBs } from 'utils/billing';
|
||||||
|
import { PlanCard } from './planCard';
|
||||||
|
|
||||||
|
const Plans = ({ plans, planPeriod, subscription, onPlanSelect }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-around',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
minHeight: '212px',
|
||||||
|
margin: '5px 0',
|
||||||
|
}}>
|
||||||
|
{plans
|
||||||
|
?.filter((plan) => plan.period === planPeriod)
|
||||||
|
?.map((plan) => (
|
||||||
|
<PlanCard
|
||||||
|
key={plan.stripeID}
|
||||||
|
isUserSubscribedPlan={isUserSubscribedPlan}
|
||||||
|
plan={plan}
|
||||||
|
subscription={subscription}
|
||||||
|
onPlanSelect={onPlanSelect}
|
||||||
|
convertBytesToGBs={convertBytesToGBs}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Plans;
|
72
src/components/pages/gallery/PlanSelector/plans/planCard.tsx
Normal file
72
src/components/pages/gallery/PlanSelector/plans/planCard.tsx
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import ArrowEast from 'components/icons/ArrowEast';
|
||||||
|
import React from 'react';
|
||||||
|
import { Button } from 'react-bootstrap';
|
||||||
|
import constants from 'utils/strings/constants';
|
||||||
|
import { PlanTile } from './planTile';
|
||||||
|
|
||||||
|
export function PlanCard({
|
||||||
|
isUserSubscribedPlan,
|
||||||
|
plan,
|
||||||
|
subscription,
|
||||||
|
onPlanSelect,
|
||||||
|
convertBytesToGBs,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<PlanTile
|
||||||
|
key={plan.stripeID}
|
||||||
|
className="subscription-plan-selector"
|
||||||
|
currentlySubscribed={isUserSubscribedPlan(plan, subscription)}
|
||||||
|
onClick={
|
||||||
|
isUserSubscribedPlan(plan, subscription)
|
||||||
|
? () => {}
|
||||||
|
: async () => await onPlanSelect(plan)
|
||||||
|
}>
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: '#ECECEC',
|
||||||
|
fontWeight: 900,
|
||||||
|
fontSize: '40px',
|
||||||
|
lineHeight: '40px',
|
||||||
|
}}>
|
||||||
|
{convertBytesToGBs(plan.storage, 0)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: '#858585',
|
||||||
|
fontSize: '24px',
|
||||||
|
fontWeight: 900,
|
||||||
|
}}>
|
||||||
|
{' '}
|
||||||
|
GB
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="bold-text"
|
||||||
|
style={{
|
||||||
|
color: '#aaa',
|
||||||
|
lineHeight: '36px',
|
||||||
|
fontSize: '20px',
|
||||||
|
}}>
|
||||||
|
{`${plan.price} / ${plan.period}`}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline-success"
|
||||||
|
block
|
||||||
|
style={{
|
||||||
|
marginTop: '20px',
|
||||||
|
fontSize: '14px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
disabled={isUserSubscribedPlan(plan, subscription)}>
|
||||||
|
{constants.CHOOSE_PLAN_BTN}
|
||||||
|
<ArrowEast
|
||||||
|
style={{
|
||||||
|
marginLeft: '5px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</PlanTile>
|
||||||
|
);
|
||||||
|
}
|
48
src/components/pages/gallery/PlanSelector/plans/planTile.tsx
Normal file
48
src/components/pages/gallery/PlanSelector/plans/planTile.tsx
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import styled, { css } from 'styled-components';
|
||||||
|
|
||||||
|
export const PlanTile = styled.div<{ currentlySubscribed: boolean }>`
|
||||||
|
border-radius: 20px;
|
||||||
|
width: 220px;
|
||||||
|
border: 2px solid #333;
|
||||||
|
padding: 30px;
|
||||||
|
margin: 10px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
background-color: #ffffff00;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
cursor: ${(props) =>
|
||||||
|
props.currentlySubscribed ? 'not-allowed' : 'pointer'};
|
||||||
|
border-color: ${(props) => props.currentlySubscribed && '#56e066'};
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& > div:first-child::before {
|
||||||
|
content: ' ';
|
||||||
|
height: 600px;
|
||||||
|
width: 50px;
|
||||||
|
background-color: #444;
|
||||||
|
left: 0;
|
||||||
|
top: -50%;
|
||||||
|
position: absolute;
|
||||||
|
transform: rotate(45deg) translateX(-200px);
|
||||||
|
transition: all 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
${(props) =>
|
||||||
|
!props.currentlySubscribed &&
|
||||||
|
css`
|
||||||
|
{
|
||||||
|
transform: scale(1.1);
|
||||||
|
background-color: #ffffff11;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
&:hover
|
||||||
|
> div:first-child::before {
|
||||||
|
transform: rotate(45deg) translateX(300px);
|
||||||
|
}
|
||||||
|
`;
|
|
@ -149,7 +149,7 @@ export default function Gallery() {
|
||||||
count: 0,
|
count: 0,
|
||||||
collectionID: 0,
|
collectionID: 0,
|
||||||
});
|
});
|
||||||
const [planModalView, setPlanModalView] = useState(false);
|
const [planModalView, setPlanModalView] = useState(true);
|
||||||
const [blockingLoad, setBlockingLoad] = useState(false);
|
const [blockingLoad, setBlockingLoad] = useState(false);
|
||||||
const [collectionSelectorAttributes, setCollectionSelectorAttributes] =
|
const [collectionSelectorAttributes, setCollectionSelectorAttributes] =
|
||||||
useState<CollectionSelectorAttributes>(null);
|
useState<CollectionSelectorAttributes>(null);
|
||||||
|
|
Loading…
Reference in a new issue