From 1abd62964ffa6abbdcf492a91da1deec9bc54642 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Thu, 9 Jun 2022 16:20:33 +0530 Subject: [PATCH] update plan selector to use mui dialog --- src/components/pages/gallery/LinkButton.tsx | 5 +- src/components/pages/gallery/PlanSelector.tsx | 417 ------------------ .../pages/gallery/PlanSelector/index.tsx | 184 ++++++++ .../PlanSelector/manageSubscription.tsx | 122 +++++ .../gallery/PlanSelector/periodToggler.tsx | 41 ++ .../gallery/PlanSelector/plans/index.tsx | 29 ++ .../gallery/PlanSelector/plans/planCard.tsx | 72 +++ .../gallery/PlanSelector/plans/planTile.tsx | 48 ++ src/pages/gallery/index.tsx | 2 +- 9 files changed, 498 insertions(+), 422 deletions(-) delete mode 100644 src/components/pages/gallery/PlanSelector.tsx create mode 100644 src/components/pages/gallery/PlanSelector/index.tsx create mode 100644 src/components/pages/gallery/PlanSelector/manageSubscription.tsx create mode 100644 src/components/pages/gallery/PlanSelector/periodToggler.tsx create mode 100644 src/components/pages/gallery/PlanSelector/plans/index.tsx create mode 100644 src/components/pages/gallery/PlanSelector/plans/planCard.tsx create mode 100644 src/components/pages/gallery/PlanSelector/plans/planTile.tsx diff --git a/src/components/pages/gallery/LinkButton.tsx b/src/components/pages/gallery/LinkButton.tsx index ee6339ec7..11556f40d 100644 --- a/src/components/pages/gallery/LinkButton.tsx +++ b/src/components/pages/gallery/LinkButton.tsx @@ -38,10 +38,7 @@ const LinkButton: FC> = ({ diff --git a/src/components/pages/gallery/PlanSelector.tsx b/src/components/pages/gallery/PlanSelector.tsx deleted file mode 100644 index be192cbaa..000000000 --- a/src/components/pages/gallery/PlanSelector.tsx +++ /dev/null @@ -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(null); - const [planPeriod, setPlanPeriod] = useState(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) => ( - {} - : async () => await onPlanSelect(plan) - }> -
- - {convertBytesToGBs(plan.storage, 0)} - - - {' '} - GB - -
-
- {`${plan.price} / ${plan.period}`} -
- -
- )); - return ( - - - - - {hasPaidSubscription(subscription) - ? constants.MANAGE_PLAN - : constants.CHOOSE_PLAN} - - - - - -
- - {constants.MONTHLY} - - - - - {constants.YEARLY} - -
-
-
- {plans && PlanIcons} -
- - {hasPaidSubscription(subscription) ? ( - <> - {hasStripeSubscription(subscription) && ( - <> - {isSubscriptionCancelled(subscription) ? ( - - 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} - - ) : ( - - 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} - - )} - - {constants.MANAGEMENT_PORTAL} - - - )} - - {constants.MANAGE_FAMILY_PORTAL} - - - ) : ( - - {isOnFreePlan(subscription) - ? constants.SKIP - : constants.CLOSE} - - )} - -
-
- ); -} - -export default PlanSelector; diff --git a/src/components/pages/gallery/PlanSelector/index.tsx b/src/components/pages/gallery/PlanSelector/index.tsx new file mode 100644 index 000000000..98541be90 --- /dev/null +++ b/src/components/pages/gallery/PlanSelector/index.tsx @@ -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(null); + const [planPeriod, setPlanPeriod] = useState(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 ( + + + + + + ); +} + +export default PlanSelector; diff --git a/src/components/pages/gallery/PlanSelector/manageSubscription.tsx b/src/components/pages/gallery/PlanSelector/manageSubscription.tsx new file mode 100644 index 000000000..a9b9942e0 --- /dev/null +++ b/src/components/pages/gallery/PlanSelector/manageSubscription.tsx @@ -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 ( + + {hasPaidSubscription(subscription) ? ( + <> + {hasStripeSubscription(subscription) && ( + <> + {isSubscriptionCancelled(subscription) ? ( + + 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} + + ) : ( + + 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} + + )} + + {constants.MANAGEMENT_PORTAL} + + + )} + + {constants.MANAGE_FAMILY_PORTAL} + + + ) : ( + + {isOnFreePlan(subscription) + ? constants.SKIP + : constants.CLOSE} + + )} + + ); +} diff --git a/src/components/pages/gallery/PlanSelector/periodToggler.tsx b/src/components/pages/gallery/PlanSelector/periodToggler.tsx new file mode 100644 index 000000000..2a01f2e46 --- /dev/null +++ b/src/components/pages/gallery/PlanSelector/periodToggler.tsx @@ -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 ( + +
+ + {constants.MONTHLY} + + + + + {constants.YEARLY} + +
+
+ ); +} diff --git a/src/components/pages/gallery/PlanSelector/plans/index.tsx b/src/components/pages/gallery/PlanSelector/plans/index.tsx new file mode 100644 index 000000000..9ce08e8f6 --- /dev/null +++ b/src/components/pages/gallery/PlanSelector/plans/index.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { isUserSubscribedPlan, convertBytesToGBs } from 'utils/billing'; +import { PlanCard } from './planCard'; + +const Plans = ({ plans, planPeriod, subscription, onPlanSelect }) => ( +
+ {plans + ?.filter((plan) => plan.period === planPeriod) + ?.map((plan) => ( + + ))} +
+); + +export default Plans; diff --git a/src/components/pages/gallery/PlanSelector/plans/planCard.tsx b/src/components/pages/gallery/PlanSelector/plans/planCard.tsx new file mode 100644 index 000000000..3c28da252 --- /dev/null +++ b/src/components/pages/gallery/PlanSelector/plans/planCard.tsx @@ -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 ( + {} + : async () => await onPlanSelect(plan) + }> +
+ + {convertBytesToGBs(plan.storage, 0)} + + + {' '} + GB + +
+
+ {`${plan.price} / ${plan.period}`} +
+ +
+ ); +} diff --git a/src/components/pages/gallery/PlanSelector/plans/planTile.tsx b/src/components/pages/gallery/PlanSelector/plans/planTile.tsx new file mode 100644 index 000000000..ba42cbe16 --- /dev/null +++ b/src/components/pages/gallery/PlanSelector/plans/planTile.tsx @@ -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); + } +`; diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx index 2ae56524f..6c511a9c3 100644 --- a/src/pages/gallery/index.tsx +++ b/src/pages/gallery/index.tsx @@ -149,7 +149,7 @@ export default function Gallery() { count: 0, collectionID: 0, }); - const [planModalView, setPlanModalView] = useState(false); + const [planModalView, setPlanModalView] = useState(true); const [blockingLoad, setBlockingLoad] = useState(false); const [collectionSelectorAttributes, setCollectionSelectorAttributes] = useState(null);