diff --git a/packages/dashboard-v2/gatsby-browser.js b/packages/dashboard-v2/gatsby-browser.js index 030fcadd..927fd206 100644 --- a/packages/dashboard-v2/gatsby-browser.js +++ b/packages/dashboard-v2/gatsby-browser.js @@ -1,5 +1,7 @@ import * as React from "react"; import { SWRConfig } from "swr"; +import { Elements } from "@stripe/react-stripe-js"; +import { loadStripe } from "@stripe/stripe-js"; import "@fontsource/sora/300.css"; // light import "@fontsource/sora/400.css"; // normal import "@fontsource/sora/500.css"; // medium @@ -11,15 +13,19 @@ import swrConfig from "./src/lib/swrConfig"; import { MODAL_ROOT_ID } from "./src/components/Modal"; import { PortalSettingsProvider } from "./src/contexts/portal-settings"; +const stripePromise = loadStripe(process.env.GATSBY_STRIPE_PUBLISHABLE_KEY); + export function wrapPageElement({ element, props }) { const Layout = element.type.Layout ?? React.Fragment; return ( - - {element} -
- + + + {element} +
+ + ); diff --git a/packages/dashboard-v2/gatsby-ssr.js b/packages/dashboard-v2/gatsby-ssr.js index 030fcadd..927fd206 100644 --- a/packages/dashboard-v2/gatsby-ssr.js +++ b/packages/dashboard-v2/gatsby-ssr.js @@ -1,5 +1,7 @@ import * as React from "react"; import { SWRConfig } from "swr"; +import { Elements } from "@stripe/react-stripe-js"; +import { loadStripe } from "@stripe/stripe-js"; import "@fontsource/sora/300.css"; // light import "@fontsource/sora/400.css"; // normal import "@fontsource/sora/500.css"; // medium @@ -11,15 +13,19 @@ import swrConfig from "./src/lib/swrConfig"; import { MODAL_ROOT_ID } from "./src/components/Modal"; import { PortalSettingsProvider } from "./src/contexts/portal-settings"; +const stripePromise = loadStripe(process.env.GATSBY_STRIPE_PUBLISHABLE_KEY); + export function wrapPageElement({ element, props }) { const Layout = element.type.Layout ?? React.Fragment; return ( - - {element} -
- + + + {element} +
+ + ); diff --git a/packages/dashboard-v2/package.json b/packages/dashboard-v2/package.json index 2e17c56b..53edfa12 100644 --- a/packages/dashboard-v2/package.json +++ b/packages/dashboard-v2/package.json @@ -22,6 +22,8 @@ "dependencies": { "@fontsource/sora": "^4.5.3", "@fontsource/source-sans-pro": "^4.5.3", + "@stripe/react-stripe-js": "^1.7.1", + "@stripe/stripe-js": "^1.27.0", "classnames": "^2.3.1", "copy-text-to-clipboard": "^3.0.1", "dayjs": "^1.10.8", diff --git a/packages/dashboard-v2/src/contexts/plans/PlansProvider.js b/packages/dashboard-v2/src/contexts/plans/PlansProvider.js index 7c6579ad..906bd7f4 100644 --- a/packages/dashboard-v2/src/contexts/plans/PlansProvider.js +++ b/packages/dashboard-v2/src/contexts/plans/PlansProvider.js @@ -19,14 +19,16 @@ const aggregatePlansAndLimits = (plans, limits, { includeFreePlan }) => { // Decorate each plan with its corresponding limits data, if available. if (limits?.length) { - return limits.map((limitsDescriptor, index) => { - const asssociatedPlan = sortedPlans.find((plan) => plan.tier === index) || {}; + return limits + .map((limitsDescriptor, index) => { + const asssociatedPlan = sortedPlans.find((plan) => plan.tier === index) || {}; - return { - ...asssociatedPlan, - limits: limitsDescriptor || null, - }; - }); + return { + ...asssociatedPlan, + limits: limitsDescriptor || null, + }; + }) + .slice(includeFreePlan ? 1 : 2); } // If we don't have the limits data yet, set just return the plans. diff --git a/packages/dashboard-v2/src/pages/upgrade.js b/packages/dashboard-v2/src/pages/upgrade.js index f3a531da..61973ec0 100644 --- a/packages/dashboard-v2/src/pages/upgrade.js +++ b/packages/dashboard-v2/src/pages/upgrade.js @@ -1,5 +1,7 @@ import * as React from "react"; import styled from "styled-components"; +import { useStripe } from "@stripe/react-stripe-js"; +import cn from "classnames"; import { useUser } from "../contexts/user"; import { PlansProvider } from "../contexts/plans/PlansProvider"; @@ -13,7 +15,9 @@ import { usePortalSettings } from "../contexts/portal-settings"; import { Alert } from "../components/Alert"; import HighlightedLink from "../components/HighlightedLink"; import { Metadata } from "../components/Metadata"; +import accountsService from "../services/accountsService"; import humanBytes from "../lib/humanBytes"; +import { Modal } from "../components/Modal"; const PAID_PORTAL_BREAKPOINTS = [ { @@ -77,6 +81,11 @@ const PlansSlider = () => { const { user, error: userError } = useUser(); const { plans, loading, activePlan, error: plansError } = useActivePlan(user); const { settings } = usePortalSettings(); + const [showPaymentError, setShowPaymentError] = React.useState(true); + const stripe = useStripe(); + // This will be the base plan that we compare upload/download speeds against. + // On will either be the user's active plan or lowest of available tiers. + const basePlan = activePlan || plans[0]; if (userError || plansError) { return ( @@ -87,6 +96,22 @@ const PlansSlider = () => { ); } + const handleSubscribe = async (selectedPlan) => { + try { + const { sessionId } = await accountsService + .post("stripe/checkout", { + json: { + price: selectedPlan.stripe, + }, + }) + .json(); + await stripe.redirectToCheckout({ sessionId }); + } catch (error) { + console.log(error); + setShowPaymentError(true); + } + }; + return (
@@ -108,40 +133,90 @@ const PlansSlider = () => { { const isHigherThanCurrent = plan.tier > activePlan?.tier; + const isCurrentPlanPaid = activePlan?.tier > 1; const isCurrent = plan.tier === activePlan?.tier; + const isLower = plan.tier < activePlan?.tier; + const speed = plan.limits.uploadBandwidth; + const currentSpeed = basePlan?.limits?.uploadBandwidth; + const speedChange = speed > currentSpeed ? speed / currentSpeed : currentSpeed / speed; + const hasActivePlan = Boolean(activePlan); return ( - + + {isCurrent && ( +
+ + Current plan + +
+ )}

{plan.name}

{plan.description}
- + {(!hasActivePlan || isHigherThanCurrent) && + (isCurrentPlanPaid ? ( + + ) : ( + + ))} + {isCurrent && } + {isLower && ( + + )}
{plan.limits && (
    - Pin up to {storage(plan.limits.storageLimit)} of censorship-resistant storage + Pin up to {storage(plan.limits.storageLimit)} on decentralized storage Support for up to {localizedNumber(plan.limits.maxNumberUploads)} files - {bandwidth(plan.limits.uploadBandwidth)} upload bandwidth - {bandwidth(plan.limits.downloadBandwidth)} download bandwidth + + {speed === currentSpeed + ? `${bandwidth(plan.limits.uploadBandwidth)} upload and ${bandwidth( + plan.limits.downloadBandwidth + )} download` + : `${speedChange}X ${ + speed > currentSpeed ? "faster" : "slower" + } upload and download speeds (${bandwidth(plan.limits.uploadBandwidth)} and ${bandwidth( + plan.limits.downloadBandwidth + )})`} + + + {plan.limits.maxUploadSize === plan.limits.storageLimit + ? "No limit to file upload size" + : `Upload files up to ${storage(plan.limits.maxUploadSize)}`} +
)}
); })} breakpoints={settings.isSubscriptionRequired ? PAID_PORTAL_BREAKPOINTS : FREE_PORTAL_BREAKPOINTS} - className="px-8 sm:px-4 md:px-0 lg:px-0" + className="px-8 sm:px-4 md:px-0 lg:px-0 mt-10" /> )} + {showPaymentError && ( + setShowPaymentError(false)}> +

Oops! 😔

+

There was an error contacting our payments provider

+

Please try again later

+
+ )}
); }; diff --git a/packages/dashboard-v2/yarn.lock b/packages/dashboard-v2/yarn.lock index bf3123ca..ae43c2db 100644 --- a/packages/dashboard-v2/yarn.lock +++ b/packages/dashboard-v2/yarn.lock @@ -3107,6 +3107,18 @@ resolve-from "^5.0.0" store2 "^2.12.0" +"@stripe/react-stripe-js@^1.7.1": + version "1.7.1" + resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.7.1.tgz#6e1db8f4a0eaf2193b153173d4aa7c38b681310d" + integrity sha512-GiUPoMo0xVvmpRD6JR9JAhAZ0W3ZpnYZNi0KE+91+tzrSFVpChKZbeSsJ5InlZhHFk9NckJCt1wOYBTqNsvt3A== + dependencies: + prop-types "^15.7.2" + +"@stripe/stripe-js@^1.27.0": + version "1.27.0" + resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.27.0.tgz#ab0c82fa89fd40260de4414f69868b769e810550" + integrity sha512-SEiybUBu+tlsFKuzdFFydxxjkbrdzHo0tz/naYC5Dt9or/Ux2gcKJBPYQ4RmqQCNHFxgyNj6UYsclywwhe2inQ== + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-1.1.2.tgz#b1665e2c461a2cd92f4c1bbf50d5454de0d4b421"