diff --git a/app/core/hooks/use-subscription.ts b/app/core/hooks/use-subscription.ts index 8326c0e..bac7218 100644 --- a/app/core/hooks/use-subscription.ts +++ b/app/core/hooks/use-subscription.ts @@ -93,12 +93,9 @@ export default function useSubscription({ initialData }: Params = {}) { }; async function changePlan({ planId }: ChangePlanParams) { - if (planId === -1) { - return cancelSubscription({ cancelUrl: subscription!.cancelUrl }); - } - try { await updateSubscriptionMutation({ planId }); + setIsWaitingForSubChange(true); } catch (error) { console.log("error", error); } diff --git a/app/settings/components/billing/plans.tsx b/app/settings/components/billing/plans.tsx index ac0bb47..e6ffcac 100644 --- a/app/settings/components/billing/plans.tsx +++ b/app/settings/components/billing/plans.tsx @@ -1,68 +1,87 @@ +import { useState } from "react"; import * as Panelbear from "@panelbear/panelbear-js"; import clsx from "clsx"; import type { Subscription } from "db"; import { SubscriptionStatus } from "db"; import useSubscription from "app/core/hooks/use-subscription"; +import SwitchPlanModal from "./switch-plan-modal"; + +export type Plan = typeof pricing["tiers"][number]; export default function Plans() { const { hasActiveSubscription, subscription, subscribe, changePlan } = useSubscription(); + const [nextPlan, setNextPlan] = useState(null); + const [isSwitchPlanModalOpen, setIsSwitchPlanModalOpen] = useState(false); return ( -
- {pricing.tiers.map((tier) => { - const isCurrentTier = subscription?.paddlePlanId === tier.planId; - const isActiveTier = hasActiveSubscription && isCurrentTier; - const cta = getCTA({ subscription, tier }); + <> +
+ {pricing.tiers.map((tier) => { + const isCurrentTier = subscription?.paddlePlanId === tier.planId; + const isActiveTier = hasActiveSubscription && isCurrentTier; + const cta = getCTA({ subscription, tier }); - return ( -
-
-

{tier.title}

- {tier.yearly ? ( -

- Get 2 months free! -

- ) : null} -

- {tier.price}€ - {tier.frequency} -

- {tier.yearly ? ( -

Billed yearly ({tier.price * 12}€)

- ) : null} -

{tier.description}

-
- - -
- ); - })} -
+
+

{tier.title}

+ {tier.yearly ? ( +

+ Get 2 months free! +

+ ) : null} +

+ {tier.price}€ + {tier.frequency} +

+ {tier.yearly ? ( +

Billed yearly ({tier.price * 12}€)

+ ) : null} +

{tier.description}

+
+ + +
+ ); + })} + + + { + changePlan({ planId: nextPlan.planId }); + Panelbear.track(`Subscribe to ${nextPlan.title}`); + setIsSwitchPlanModalOpen(false); + }} + closeModal={() => setIsSwitchPlanModalOpen(false)} + /> + ); } diff --git a/app/settings/components/billing/switch-plan-modal.tsx b/app/settings/components/billing/switch-plan-modal.tsx new file mode 100644 index 0000000..4452696 --- /dev/null +++ b/app/settings/components/billing/switch-plan-modal.tsx @@ -0,0 +1,52 @@ +import type { FunctionComponent } from "react"; +import { useRef } from "react"; + +import Modal, { ModalTitle } from "app/core/components/modal"; +import type { Plan } from "./plans"; + +type Props = { + isOpen: boolean; + nextPlan: Plan | null; + confirm: (nextPlan: Plan) => void; + closeModal: () => void; +}; + +const SwitchPlanModal: FunctionComponent = ({ isOpen, nextPlan, confirm, closeModal }) => { + const confirmButtonRef = useRef(null); + + return ( + +
+
+ Are you sure you want to switch to {nextPlan?.title}? +
+

+ You're about to switch to the {nextPlan?.title} plan. You will be + billed immediately a prorated amount and the next billing date will be recalculated from + today. +

+
+
+
+
+ + +
+
+ ); +}; + +export default SwitchPlanModal; diff --git a/app/settings/pages/settings/billing.tsx b/app/settings/pages/settings/billing.tsx index 6f36636..3a99896 100644 --- a/app/settings/pages/settings/billing.tsx +++ b/app/settings/pages/settings/billing.tsx @@ -20,12 +20,6 @@ type Props = { }; const Billing: BlitzPage = (props) => { - /* - TODO: I want to be able to - - upgrade to yearly - - downgrade to monthly - */ - const { count: paymentsCount } = usePaymentsHistory(); const { subscription, cancelSubscription, updatePaymentMethod } = useSubscription({ initialData: props.subscription, @@ -37,8 +31,8 @@ const Billing: BlitzPage = (props) => { {subscription.status === SubscriptionStatus.deleted ? (

- Your {subscription.paddlePlanId} subscription is cancelled and will expire on{" "} - {subscription.cancellationEffectiveDate!.toLocaleDateString()}. + Your {plansName[subscription.paddlePlanId]?.toLowerCase()} subscription is cancelled and + will expire on {subscription.cancellationEffectiveDate!.toLocaleDateString()}.

) : ( <> @@ -72,6 +66,11 @@ const Billing: BlitzPage = (props) => { ); }; +const plansName: Record = { + 727544: "Yearly", + 727540: "Monthly", +}; + Billing.getLayout = (page) => {page}; export const getServerSideProps: GetServerSideProps = async ({ req, res }) => { diff --git a/integrations/paddle.ts b/integrations/paddle.ts index f4ff5d6..dc47707 100644 --- a/integrations/paddle.ts +++ b/integrations/paddle.ts @@ -93,12 +93,14 @@ export async function updateSubscriptionPlan({ productId, shouldProrate = true, shouldKeepModifiers = true, + shouldMakeImmediatePayment = true, }: PaddleSdkUpdateSubscriptionRequest) { return paddleSdk.updateSubscription({ subscriptionId, productId, shouldProrate, shouldKeepModifiers, + shouldMakeImmediatePayment, }); }