From 4c7b591b0e1b620b415c0294f50af3b7b629078a Mon Sep 17 00:00:00 2001 From: Richard Date: Tue, 7 Jan 2020 16:17:37 +0100 Subject: [PATCH 1/4] Use new subscription flow --- src/app/app.scss | 1 + .../components/sections/plans/PlansSection.js | 169 ------------------ src/app/containers/DashboardContainer.js | 57 +----- 3 files changed, 3 insertions(+), 224 deletions(-) delete mode 100644 src/app/components/sections/plans/PlansSection.js diff --git a/src/app/app.scss b/src/app/app.scss index 8430651f..acbdde46 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -19,6 +19,7 @@ $color-links: $pm-primary; @import '~design-system/_sass/pv-styles/pv-plan-card'; @import './containers/RedeemContainer.scss'; @import '~react-components/containers/payments/subscription/BlackFridayModal.scss'; +@import '~react-components/containers/payments/subscription/SubscriptionTable.scss'; // stuff for responsive @import '~design-system/_sass/reusable-components/design-system-responsive'; diff --git a/src/app/components/sections/plans/PlansSection.js b/src/app/components/sections/plans/PlansSection.js deleted file mode 100644 index daafdd20..00000000 --- a/src/app/components/sections/plans/PlansSection.js +++ /dev/null @@ -1,169 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - Alert, - SubTitle, - useApi, - usePlans, - Loader, - useSubscription, - DowngradeModal, - LossLoyaltyModal, - useModals, - SubscriptionModal, - useLoading, - useEventManager, - useNotifications, - useUser, - ConfirmModal, - useOrganization -} from 'react-components'; -import { c } from 'ttag'; -import { DEFAULT_CURRENCY, DEFAULT_CYCLE } from 'proton-shared/lib/constants'; -import { checkSubscription, deleteSubscription } from 'proton-shared/lib/api/payments'; -import { isBundleEligible, getPlans, getPlan } from 'proton-shared/lib/helpers/subscription'; -import { isLoyal } from 'proton-shared/lib/helpers/organization'; -import { mergePlansMap } from 'react-components/containers/payments/subscription/helpers'; -import { PLANS } from 'proton-shared/lib/constants'; - -import PlansTable from './PlansTable'; - -const PlansSection = () => { - const api = useApi(); - const [user] = useUser(); - const { isFree, isPaid } = user; - const { call } = useEventManager(); - const { createNotification } = useNotifications(); - const { createModal } = useModals(); - const [loading, withLoading] = useLoading(); - const [currency, updateCurrency] = useState(DEFAULT_CURRENCY); - const [cycle, updateCycle] = useState(DEFAULT_CYCLE); - const [plans, loadingPlans] = usePlans(); - const [subscription, loadingSubscription] = useSubscription(); - const [organization, loadingOrganization] = useOrganization(); - const bundleEligible = isBundleEligible(subscription); - const names = getPlans(subscription) - .map(({ Title }) => Title) - .join(c('Separator, spacing is important').t` and `); - const { CouponCode, Plans = [] } = subscription || {}; - - const unsubscribe = async () => { - if (isFree) { - return createNotification({ type: 'error', text: c('Info').t`You already have a free account` }); - } - - const { Name = '' } = getPlan(subscription); - - if (Name && Name !== PLANS.VISIONARY) { - await new Promise((resolve, reject) => { - createModal( - - {c('Info') - .t`This will downgrade your VPN to a free subscription. ProtonVPN is free software that is possible thanks to donations and paid accounts. Please consider making a donation so we can continue to enable secure internet anywhere for everyone.`} - - ); - }); - // eslint-disable-next-line @typescript-eslint/no-use-before-define - return handleSelectPlan(Name); - } - - await new Promise((resolve, reject) => { - createModal(); - }); - if (isLoyal(organization)) { - await new Promise((resolve, reject) => { - createModal(); - }); - } - await api(deleteSubscription()); - await call(); - createNotification({ text: c('Success').t`You have successfully unsubscribed` }); - }; - - const handleSelectPlan = async (planName) => { - if (!planName) { - return unsubscribe(); - } - - const plansMap = mergePlansMap({ [planName]: 1 }, subscription); - const couponCode = CouponCode ? CouponCode : undefined; // From current subscription; CouponCode can be null - const PlanIDs = Object.entries(plansMap).reduce((acc, [planName, quantity]) => { - if (quantity) { - const { ID } = plans.find((plan) => plan.Name === planName); - acc[ID] = quantity; - } - return acc; - }, Object.create(null)); - - const { Coupon } = await api( - checkSubscription({ - PlanIDs, - CouponCode: couponCode, - Currency: currency, - Cycle: cycle - }) - ); - - const coupon = Coupon ? Coupon.Code : undefined; // Coupon can equals null - - createModal( - - ); - }; - - useEffect(() => { - if (isFree) { - const [{ Currency } = {}] = plans || []; - updateCurrency(Currency); - } - }, [plans]); - - useEffect(() => { - if (isPaid) { - const { Currency, Cycle } = subscription || {}; - updateCurrency(Currency); - updateCycle(Cycle); - } - }, [subscription]); - - if (loadingPlans || loadingSubscription || loadingOrganization) { - return ( - <> - {c('Title').t`Plans`} - - - ); - } - - return ( - <> - {c('Title').t`Plans`} - - {bundleEligible ? ( -
{c('Info') - .t`Get 20% bundle discount when you purchase ProtonMail and ProtonVPN together.`}
- ) : null} - {Plans.length ?
{c('Info').t`You are currently subscribed to ${names}.`}
: null} -
-
- () => withLoading(handleSelectPlan(planName))} - loading={loading} - currency={currency} - cycle={cycle} - updateCurrency={updateCurrency} - updateCycle={updateCycle} - plans={plans} - subscription={subscription} - /> -
- - ); -}; - -export default PlansSection; diff --git a/src/app/containers/DashboardContainer.js b/src/app/containers/DashboardContainer.js index e78ed890..6d45387f 100644 --- a/src/app/containers/DashboardContainer.js +++ b/src/app/containers/DashboardContainer.js @@ -1,22 +1,9 @@ -import React, { useEffect, useRef } from 'react'; -import { - SubscriptionSection, - BillingSection, - useModals, - VPNBlackFridayModal, - usePlans, - SubscriptionModal, - useSubscription, - useBlackFriday, - useUser, - useApi -} from 'react-components'; -import { checkLastCancelledSubscription } from 'react-components/containers/payments/subscription/helpers'; +import React from 'react'; +import { PlansSection, SubscriptionSection, BillingSection } from 'react-components'; import { PERMISSIONS } from 'proton-shared/lib/constants'; import { c } from 'ttag'; import Page from '../components/page/Page'; -import PlansSection from '../components/sections/plans/PlansSection'; const { UPGRADER, PAID } = PERMISSIONS; @@ -46,46 +33,6 @@ export const getDashboardPage = () => { }; const DashboardContainer = () => { - const api = useApi(); - const { createModal } = useModals(); - const [plans, loadingPlans] = usePlans(); - const [subscription] = useSubscription(); - const isBlackFriday = useBlackFriday(); - const checked = useRef(false); - const [user] = useUser(); - - const handleSelect = ({ planIDs = [], cycle, currency, couponCode }) => { - const plansMap = planIDs.reduce((acc, planID) => { - const { Name } = plans.find(({ ID }) => ID === planID); - acc[Name] = 1; - return acc; - }, Object.create(null)); - - createModal( - - ); - }; - - const check = async () => { - if (await checkLastCancelledSubscription(api)) { - createModal(); - } - }; - - useEffect(() => { - if (Array.isArray(plans) && !checked.current && user.isFree && isBlackFriday) { - check(); - checked.current = true; - } - }, [loadingPlans]); - return ( From 1ed5d639295d58cd6c426bd609d6d1e777f9837d Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 8 Jan 2020 13:59:20 +0100 Subject: [PATCH 2/4] Review --- .../PaymentStep/PaymentStep.js | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/app/containers/SignupContainer/PaymentStep/PaymentStep.js b/src/app/containers/SignupContainer/PaymentStep/PaymentStep.js index f1ea7a4a..cf10da90 100644 --- a/src/app/containers/SignupContainer/PaymentStep/PaymentStep.js +++ b/src/app/containers/SignupContainer/PaymentStep/PaymentStep.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Alert, Payment, usePayment, PrimaryButton, Field, Row, useLoading, SubTitle, useCard } from 'react-components'; +import { Alert, Payment, usePayment, PrimaryButton, Field, Row, useLoading, SubTitle } from 'react-components'; import { c } from 'ttag'; import { PAYMENT_METHOD_TYPES, CYCLE, CURRENCIES } from 'proton-shared/lib/constants'; @@ -8,8 +8,13 @@ import LoginPanel from '../LoginPanel'; const PaymentStep = ({ onPay, paymentAmount, model, children }) => { const [loading, withLoading] = useLoading(); - const { method, setMethod, parameters, canPay, setParameters, setCardValidity } = usePayment(); - const card = useCard(); + const { card, setCard, errors, method, setMethod, parameters, canPay, paypal, paypalCredit } = usePayment({ + amount: paymentAmount, + currency: model.currency, + onPay(params) { + withLoading(onPay(model, params)); + } + }); return (
@@ -18,18 +23,17 @@ const PaymentStep = ({ onPay, paymentAmount, model, children }) => {
{c('Info').t`Your payment details are protected with TLS encryption and Swiss laws`} withLoading(onPay(model, params))} - fieldClassName="auto flex-item-fluid-auto" + onCard={setCard} + errors={errors} + paypal={paypal} + paypalCredit={paypalCredit} > {method === PAYMENT_METHOD_TYPES.CARD && ( From bb3afc162e2a38de541024fddd98f1eab1cee9af Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 22 Jan 2020 08:39:28 +0100 Subject: [PATCH 3/4] Review --- src/app/containers/DashboardContainer.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/app/containers/DashboardContainer.js b/src/app/containers/DashboardContainer.js index 6d45387f..25d11592 100644 --- a/src/app/containers/DashboardContainer.js +++ b/src/app/containers/DashboardContainer.js @@ -1,5 +1,5 @@ import React from 'react'; -import { PlansSection, SubscriptionSection, BillingSection } from 'react-components'; +import { PlansSection, SubscriptionSection, BillingSection, useUser } from 'react-components'; import { PERMISSIONS } from 'proton-shared/lib/constants'; import { c } from 'ttag'; @@ -7,14 +7,14 @@ import Page from '../components/page/Page'; const { UPGRADER, PAID } = PERMISSIONS; -export const getDashboardPage = () => { +export const getDashboardPage = (user = {}) => { return { text: c('Title').t`Dashboard`, route: '/dashboard', icon: 'dashboard', permissions: [UPGRADER], sections: [ - { + !user.isPaid && { text: c('Title').t`Plans`, id: 'plans' }, @@ -28,11 +28,26 @@ export const getDashboardPage = () => { id: 'billing', permissions: [PAID] } - ] + ].filter(Boolean) }; }; const DashboardContainer = () => { + const [user, loadingUser] = useUser(); + + if (loadingUser) { + return null; + } + + if (user.isPaid) { + return ( + + + + + ); + } + return ( From 98d46c413eee5a17cc599b4b7a699d910ba7c663 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 22 Jan 2020 16:17:12 +0100 Subject: [PATCH 4/4] Review --- src/app/containers/DashboardContainer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/containers/DashboardContainer.js b/src/app/containers/DashboardContainer.js index 25d11592..c8345feb 100644 --- a/src/app/containers/DashboardContainer.js +++ b/src/app/containers/DashboardContainer.js @@ -14,7 +14,7 @@ export const getDashboardPage = (user = {}) => { icon: 'dashboard', permissions: [UPGRADER], sections: [ - !user.isPaid && { + !user.hasPaidVpn && { text: c('Title').t`Plans`, id: 'plans' }, @@ -39,7 +39,7 @@ const DashboardContainer = () => { return null; } - if (user.isPaid) { + if (user.hasPaidVpn) { return (