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..c8345feb 100644
--- a/src/app/containers/DashboardContainer.js
+++ b/src/app/containers/DashboardContainer.js
@@ -1,33 +1,20 @@
-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, useUser } 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;
-export const getDashboardPage = () => {
+export const getDashboardPage = (user = {}) => {
return {
text: c('Title').t`Dashboard`,
route: '/dashboard',
icon: 'dashboard',
permissions: [UPGRADER],
sections: [
- {
+ !user.hasPaidVpn && {
text: c('Title').t`Plans`,
id: 'plans'
},
@@ -41,50 +28,25 @@ export const getDashboardPage = () => {
id: 'billing',
permissions: [PAID]
}
- ]
+ ].filter(Boolean)
};
};
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 [user, loadingUser] = useUser();
+
+ if (loadingUser) {
+ return null;
+ }
+
+ if (user.hasPaidVpn) {
+ return (
+
+
+
+
);
- };
-
- const check = async () => {
- if (await checkLastCancelledSubscription(api)) {
- createModal();
- }
- };
-
- useEffect(() => {
- if (Array.isArray(plans) && !checked.current && user.isFree && isBlackFriday) {
- check();
- checked.current = true;
- }
- }, [loadingPlans]);
+ }
return (
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 && (