diff --git a/src/components/pages/gallery/PlanSelector/card.tsx b/src/components/pages/gallery/PlanSelector/card.tsx new file mode 100644 index 000000000..1b6414752 --- /dev/null +++ b/src/components/pages/gallery/PlanSelector/card.tsx @@ -0,0 +1,202 @@ +import { PLAN_PERIOD } from './../../../../constants/gallery/index'; +import { PeriodToggler } from './periodToggler'; +import React, { useContext, useEffect, useMemo, useState } from 'react'; +import constants from 'utils/strings/constants'; +import { Plan } from 'types/billing'; +import { + isUserSubscribedPlan, + isSubscriptionCancelled, + updateSubscription, + hasStripeSubscription, + isOnFreePlan, + planForSubscription, + hasMobileSubscription, + hasPaypalSubscription, + getLocalUserSubscription, + hasPaidSubscription, + convertBytesToGBs, + getTotalFamilyUsage, + makeHumanReadableStorage, +} 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 Plans from './plans'; +import { Box, Stack, Typography } from '@mui/material'; +import { useLocalState } from 'hooks/useLocalState'; +import { LS_KEYS } from 'utils/storage/localStorage'; +import { getLocalUserDetails } from 'utils/user'; +import { ManageSubscription } from './manageSubscription'; + +interface Props { + closeModal: any; + setLoading: SetLoading; +} + +function PlanSelectorCard(props: Props) { + const subscription = useMemo(() => getLocalUserSubscription(), []); + const totalFamilyUsage = useMemo( + () => getTotalFamilyUsage(getLocalUserDetails().familyData), + [] + ); + const [plans, setPlans] = useLocalState(LS_KEYS.PLANS); + 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(() => { + 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: 'accent', + action: onReopenClick, + }, + }); + } finally { + props.setLoading(false); + } + }; + main(); + }, []); + + 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: 'accent', + }, + 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' }, + }); + } + } + } + + return ( + <> + + {hasPaidSubscription(subscription) ? ( + + + {constants.SUBSCRIPTION} + + + {convertBytesToGBs(subscription.storage)}{' '} + {constants.GB} + + + ) : ( + + {constants.CHOOSE_PLAN} + + )} + {totalFamilyUsage > 0 && ( + + {constants.CURRENT_USAGE( + makeHumanReadableStorage(totalFamilyUsage) + )} + + )} + + + + {constants.TWO_MONTHS_FREE} + + + + + + + ); +} + +export default PlanSelectorCard; diff --git a/src/components/pages/gallery/PlanSelector/index.tsx b/src/components/pages/gallery/PlanSelector/index.tsx index f0fcc053a..0eabe3220 100644 --- a/src/components/pages/gallery/PlanSelector/index.tsx +++ b/src/components/pages/gallery/PlanSelector/index.tsx @@ -1,150 +1,19 @@ -import { PeriodToggler } from './periodToggler'; -import React, { useContext, useEffect, useMemo, useState } from 'react'; -import constants from 'utils/strings/constants'; -import { Plan } from 'types/billing'; -import { - isUserSubscribedPlan, - isSubscriptionCancelled, - updateSubscription, - hasStripeSubscription, - isOnFreePlan, - planForSubscription, - hasMobileSubscription, - hasPaypalSubscription, - getLocalUserSubscription, -} from 'utils/billing'; -import { reverseString } from 'utils/common'; -import { GalleryContext } from 'pages/gallery'; -import billingService from 'services/billingService'; +import React, { useContext } from 'react'; import { SetLoading } from 'types/gallery'; -import { logError } from 'utils/sentry'; import { AppContext } from 'pages/_app'; -import Plans from './plans'; -import { Box, Dialog, Stack, Typography } from '@mui/material'; -import { useLocalState } from 'hooks/useLocalState'; -import { LS_KEYS } from 'utils/storage/localStorage'; +import { Box, Dialog } from '@mui/material'; +import PlanSelectorCard from './card'; interface Props { modalView: boolean; closeModal: any; - setLoading: SetLoading; } -export enum PLAN_PERIOD { - MONTH = 'month', - YEAR = 'year', -} function PlanSelector(props: Props) { - const subscription = useMemo(() => getLocalUserSubscription(), []); - const [plans, setPlans] = useLocalState(LS_KEYS.PLANS); - 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: 'accent', - 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: 'accent', - }, - 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' }, - }); - } - } + if (!props.modalView) { + return <>; } return ( @@ -154,35 +23,10 @@ function PlanSelector(props: Props) { onClose={props.closeModal} PaperProps={{ sx: { width: '400px' } }}> - - - { - /* hasPaidSubscription(subscription) - ? constants.MANAGE_PLAN - : */ constants.CHOOSE_PLAN - } - - - - - {constants.TWO_MONTHS_FREE} - - - - {/* */} - + /> ); diff --git a/src/components/pages/gallery/PlanSelector/periodToggler.tsx b/src/components/pages/gallery/PlanSelector/periodToggler.tsx index 5d1e4123c..3929cfcac 100644 --- a/src/components/pages/gallery/PlanSelector/periodToggler.tsx +++ b/src/components/pages/gallery/PlanSelector/periodToggler.tsx @@ -1,7 +1,7 @@ import { styled, ToggleButton, ToggleButtonGroup } from '@mui/material'; +import { PLAN_PERIOD } from 'constants/gallery'; import React from 'react'; import constants from 'utils/strings/constants'; -import { PLAN_PERIOD } from '.'; export function PeriodToggler({ planPeriod, togglePeriod }) { const CustomToggleButton = styled(ToggleButton)(({ theme }) => ({ textTransform: 'none', diff --git a/src/components/pages/gallery/PlanSelector/plans/index.tsx b/src/components/pages/gallery/PlanSelector/plans/index.tsx index ee8cede7e..df63aece4 100644 --- a/src/components/pages/gallery/PlanSelector/plans/index.tsx +++ b/src/components/pages/gallery/PlanSelector/plans/index.tsx @@ -2,6 +2,7 @@ import ArrowForward from '@mui/icons-material/ArrowForward'; import { Box, Typography } from '@mui/material'; import { FlexWrapper, SpaceBetweenFlex } from 'components/Container'; import React from 'react'; +import { isUserSubscribedPlan } from 'utils/billing'; import constants from 'utils/strings/constants'; import { PlanRow } from './planRow'; @@ -11,6 +12,7 @@ const Plans = ({ plans, planPeriod, subscription, onPlanSelect }) => ( ?.filter((plan) => plan.period === planPeriod) ?.map((plan) => ( { - !isUserSubscribedPlan(plan, subscription) && onPlanSelect(plan); - }; - - return ( - - - {convertBytesToGBs(plan.storage, 0)} - - - - {`${plan.price} / ${ - plan.period === PLAN_PERIOD.MONTH - ? constants.MONTH_SHORT - : constants.YEAR_SHORT - }`} - - - - - ); -} diff --git a/src/components/pages/gallery/PlanSelector/plans/planRow.tsx b/src/components/pages/gallery/PlanSelector/plans/planRow.tsx index 39cb12054..163b2cfef 100644 --- a/src/components/pages/gallery/PlanSelector/plans/planRow.tsx +++ b/src/components/pages/gallery/PlanSelector/plans/planRow.tsx @@ -1,51 +1,75 @@ -import { Box, Button, Typography } from '@mui/material'; +import { Box, Button, ButtonProps, styled, Typography } from '@mui/material'; import React from 'react'; import { isUserSubscribedPlan, convertBytesToGBs } from 'utils/billing'; import constants from 'utils/strings/constants'; -import { PLAN_PERIOD } from '..'; import { FlexWrapper, FluidContainer } from 'components/Container'; import ArrowForward from '@mui/icons-material/ArrowForward'; +import { PLAN_PERIOD } from 'constants/gallery'; +import Done from '@mui/icons-material/Done'; +import { Plan, Subscription } from 'types/billing'; -export function PlanRow({ plan, subscription, onPlanSelect }) { +interface Iprops { + plan: Plan; + subscription: Subscription; + onPlanSelect: (plan: Plan) => void; + disabled: boolean; +} +const DisabledPlanButton = styled((props: ButtonProps) => ( + + + + + + {plan.price}{' '} + {' '} + + / $ + {plan.period === PLAN_PERIOD.MONTH + ? constants.MONTH_SHORT + : constants.YEAR_SHORT} + + + + ); } diff --git a/src/constants/gallery/index.ts b/src/constants/gallery/index.ts index 262b6f502..e93483e3e 100644 --- a/src/constants/gallery/index.ts +++ b/src/constants/gallery/index.ts @@ -6,3 +6,8 @@ export const IMAGE_CONTAINER_MAX_WIDTH = IMAGE_CONTAINER_MAX_HEIGHT - GAP_BTW_TILES; export const MIN_COLUMNS = 4; export const SPACE_BTW_DATES = 44; + +export enum PLAN_PERIOD { + MONTH = 'month', + YEAR = 'year', +} diff --git a/src/utils/billing/index.ts b/src/utils/billing/index.ts index ad533ee6d..89a5399e6 100644 --- a/src/utils/billing/index.ts +++ b/src/utils/billing/index.ts @@ -52,7 +52,6 @@ export function convertBytesToHumanReadable( export function makeHumanReadableStorage( bytes: number, - round: 'round-up' | 'round-down' = 'round-down' ): string { const i = Math.floor(Math.log(bytes) / Math.log(1024)); @@ -139,6 +138,13 @@ export function getFamilyPlanAdmin(familyData: FamilyData): FamilyMember { } } +export function getTotalFamilyUsage(familyData: FamilyData): number { + return familyData.members.reduce( + (sum, currentMember) => sum + currentMember.usage, + 0 + ); +} + export function getLocalUserSubscription(): Subscription { return getData(LS_KEYS.SUBSCRIPTION); } diff --git a/src/utils/strings/englishConstants.tsx b/src/utils/strings/englishConstants.tsx index 05cb7dff8..b89aed79c 100644 --- a/src/utils/strings/englishConstants.tsx +++ b/src/utils/strings/englishConstants.tsx @@ -754,6 +754,7 @@ const englishConstants = { GB: 'GB', FREE_PLAN_OPTION_LABEL: 'Continue with free trial', FREE_PLAN_DESCRIPTION: '1 GB for 1 year', + CURRENT_USAGE: (usage) => `Current usage is ${usage}`, }; export default englishConstants; diff --git a/src/utils/user/index.ts b/src/utils/user/index.ts index 273160504..1fa230f7e 100644 --- a/src/utils/user/index.ts +++ b/src/utils/user/index.ts @@ -24,5 +24,5 @@ export function getUserAnonymizedID() { } export function getLocalUserDetails(): UserDetails { - return getData(LS_KEYS.USER_DETAILS); + return getData(LS_KEYS.USER_DETAILS)?.value; }