Skip to content

[UBP] Disable subscriptions to old plans after UBP has been enabled #15092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions components/dashboard/src/contexts/FeatureFlagContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ interface FeatureFlagConfig {

const FeatureFlagContext = createContext<{
showUsageView: boolean;
isUsageBasedBillingEnabled: boolean;
showUseLastSuccessfulPrebuild: boolean;
usePublicApiTeamsService: boolean;
enablePersonalAccessTokens: boolean;
}>({
showUsageView: false,
isUsageBasedBillingEnabled: false,
showUseLastSuccessfulPrebuild: false,
usePublicApiTeamsService: false,
enablePersonalAccessTokens: false,
Expand All @@ -34,6 +36,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
const location = useLocation();
const team = getCurrentTeam(location, teams);
const [showUsageView, setShowUsageView] = useState<boolean>(false);
const [isUsageBasedBillingEnabled, setIsUsageBasedBillingEnabled] = useState<boolean>(false);
const [showUseLastSuccessfulPrebuild, setShowUseLastSuccessfulPrebuild] = useState<boolean>(false);
const [usePublicApiTeamsService, setUsePublicApiTeamsService] = useState<boolean>(false);
const [enablePersonalAccessTokens, setPersonalAccessTokensEnabled] = useState<boolean>(false);
Expand All @@ -43,6 +46,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
(async () => {
const featureFlags: FeatureFlagConfig = {
usage_view: { defaultValue: false, setter: setShowUsageView },
isUsageBasedBillingEnabled: { defaultValue: false, setter: setIsUsageBasedBillingEnabled },
showUseLastSuccessfulPrebuild: { defaultValue: false, setter: setShowUseLastSuccessfulPrebuild },
publicApiExperimentalTeamsService: { defaultValue: false, setter: setUsePublicApiTeamsService },
personalAccessTokensEnabled: { defaultValue: false, setter: setPersonalAccessTokensEnabled },
Expand Down Expand Up @@ -86,6 +90,7 @@ const FeatureFlagContextProvider: React.FC = ({ children }) => {
<FeatureFlagContext.Provider
value={{
showUsageView,
isUsageBasedBillingEnabled,
showUseLastSuccessfulPrebuild,
usePublicApiTeamsService,
enablePersonalAccessTokens,
Expand Down
50 changes: 25 additions & 25 deletions components/dashboard/src/settings/Plans.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { UserContext } from "../user-context";
import Tooltip from "../components/Tooltip";
import { PaymentContext } from "../payment-context";
import { PageWithSettingsSubMenu } from "./PageWithSettingsSubMenu";
import { FeatureFlagContext } from "../contexts/FeatureFlagContext";

type PlanWithOriginalPrice = Plan & { originalPrice?: number };
type Pending = { pendingSince: number };
Expand All @@ -45,6 +46,7 @@ type TeamClaimModal =
export default function () {
const { user, userBillingMode } = useContext(UserContext);
const { currency, setCurrency, isStudent, isChargebeeCustomer } = useContext(PaymentContext);
const { isUsageBasedBillingEnabled } = useContext(FeatureFlagContext);
const [accountStatement, setAccountStatement] = useState<AccountStatement>();
const [availableCoupons, setAvailableCoupons] = useState<PlanCoupon[]>();
const [appliedCoupons, setAppliedCoupons] = useState<PlanCoupon[]>();
Expand Down Expand Up @@ -435,7 +437,7 @@ export default function () {
) : undefined;
planCards.push(
<PlanCard
isDisabled={!!assignedTs || pendingChargebeeCallback}
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
plan={applyCoupons(personalPlan, appliedCoupons)}
isCurrent={true}
bottomLabel={bottomLabel}
Expand Down Expand Up @@ -474,7 +476,7 @@ export default function () {
}
planCards.push(
<PlanCard
isDisabled={!!assignedTs || pendingChargebeeCallback}
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
plan={targetPlan}
isCurrent={false}
onUpgrade={onUpgrade}
Expand Down Expand Up @@ -505,7 +507,7 @@ export default function () {
) : undefined;
planCards.push(
<PlanCard
isDisabled={!!assignedTs || pendingChargebeeCallback}
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
plan={applyCoupons(professionalPlan, appliedCoupons)}
isCurrent={true}
bottomLabel={bottomLabel}
Expand Down Expand Up @@ -545,7 +547,7 @@ export default function () {
}
planCards.push(
<PlanCard
isDisabled={!!assignedTs || pendingChargebeeCallback}
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
plan={targetPlan}
isCurrent={!!assignedProfessionalTs}
onUpgrade={onUpgrade}
Expand Down Expand Up @@ -583,7 +585,7 @@ export default function () {
) : undefined;
planCards.push(
<PlanCard
isDisabled={!!assignedTs || pendingChargebeeCallback}
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
plan={applyCoupons(studentUnleashedPlan, appliedCoupons)}
isCurrent={true}
bottomLabel={bottomLabel}
Expand All @@ -599,7 +601,7 @@ export default function () {
) : undefined;
planCards.push(
<PlanCard
isDisabled={!!assignedTs || pendingChargebeeCallback}
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
plan={applyCoupons(unleashedPlan, appliedCoupons)}
isCurrent={true}
bottomLabel={bottomLabel}
Expand All @@ -618,7 +620,7 @@ export default function () {
}
planCards.push(
<PlanCard
isDisabled={!!assignedTs || pendingChargebeeCallback}
isDisabled={!!isUsageBasedBillingEnabled || !!assignedTs || pendingChargebeeCallback}
plan={targetPlan}
isCurrent={!!isUnleashedTsAssigned}
onUpgrade={onUpgrade}
Expand All @@ -629,20 +631,28 @@ export default function () {
);
}

const showPlans = userBillingMode && userBillingMode.mode === "chargebee";
return (
<div>
<PageWithSettingsSubMenu title="Plans" subtitle="Manage account usage and billing.">
{showPlans && (
{isUsageBasedBillingEnabled && (
<Alert type="message" className="mb-4">
Your account has been enabled for usage-based billing. Discover faster workspace classes and
only pay for what you actually use.{" "}
<a className="gp-link" href="https://www.gitpod.io/docs/configure/billing/usage-based-billing">
Learn more
</a>
<br />
<br />
The old monthly plans are deprecated and will be cancelled by End of March 2023.
</Alert>
Comment on lines +638 to +647
Copy link
Contributor

@gtsiolis gtsiolis Dec 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @jankeromnes! Some thoughts on the copy used here:

  1. Too verbose, or uses unnecessary empty space.
  2. Focus on the personal account, not team plans or team billing.
  3. Sounds like the user has been part of a closed beta list and not accessing a GA feature on Gitpod.io.
  4. Voice and tone is not direct, using terms like discover.
  5. Lack of clear action. Do we want to provide an action for users or nudge to perform an action?

What do you think of the following copy?

BEFORE AFTER
alert-before alert-after

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not call our pricing as a whole "pay-as-you-go". the pay as you go bit is only the mode where you pay later. We have a free plan and a personal plan with a certain amount of credits that is not pay-as-you-go. We'll likely have other ways to prepurchase credits in the future.
"Usage-based" is ok but I'd prefer to generally just talk about (new) pricing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DEAL 🤝

)}
<div className="w-full text-center">
<p className="text-xl text-gray-500">
You are currently using the{" "}
<span className="font-bold">
{Plans.getById(assignedTs?.planId)?.name || currentPlan.name}
</span>{" "}
<span className="font-bold">{Plans.getById(assignedTs?.planId)?.name || currentPlan.name}</span>{" "}
plan.
</p>
{!assignedTs && (
{!isUsageBasedBillingEnabled && !assignedTs && (
<p className="text-base w-96 m-auto">
Upgrade your plan to get more hours and more parallel workspaces.
</p>
Expand Down Expand Up @@ -673,9 +683,7 @@ export default function () {
className={`gp-link ${isChargebeeCustomer ? "" : "invisible"}`}
href="javascript:void(0)"
onClick={() => {
ChargebeeClient.getOrCreate().then((chargebeeClient) =>
chargebeeClient.openPortal(),
);
ChargebeeClient.getOrCreate().then((chargebeeClient) => chargebeeClient.openPortal());
}}
>
Billing
Expand Down Expand Up @@ -709,21 +717,13 @@ export default function () {
)}
</p>
</div>
)}
<div className="mt-4 flex justify-center space-x-3 2xl:space-x-7">{planCards}</div>
{assignedTs && userBillingMode?.mode === "chargebee" && !!userBillingMode.teamNames && (
<Alert type="info" className="mt-10 mx-auto">
<p>Assigned Team Seats</p>
<ul>{userBillingMode.teamNames.join(", ")}</ul>
</Alert>
)}
<InfoBox className="w-2/3 mt-14 mx-auto">
If you are interested in purchasing a plan for a team, purchase a Team plan with one centralized
billing.{" "}
<a className="underline" href="https://www.gitpod.io/docs/teams/">
Learn more
</a>
</InfoBox>
{!!confirmUpgradeToPlan && (
// TODO: Use title and buttons props
<Modal visible={true} onClose={() => setConfirmUpgradeToPlan(undefined)}>
Expand Down Expand Up @@ -875,7 +875,7 @@ interface PlanCardProps {
function PlanCard(p: PlanCardProps) {
return (
<SelectableCard
className="w-44 2xl:w-56"
className={`w-44 2xl:w-56 ${p.isDisabled ? "pointer-events-none" : ""}`}
title={p.plan.name.toUpperCase()}
selected={p.isCurrent}
onClick={() => {}}
Expand Down
48 changes: 23 additions & 25 deletions components/dashboard/src/settings/Teams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* See License-AGPL.txt in the project root for license information.
*/

import React, { useContext, useEffect, useRef, useState } from "react";
import { useContext, useEffect, useRef, useState } from "react";
import ContextMenu, { ContextMenuEntry } from "../components/ContextMenu";
import { getGitpodService } from "../service/service";
import Alert from "../components/Alert";
Expand All @@ -23,8 +23,7 @@ import { poll, PollOptions } from "../utils";
import { Disposable } from "@gitpod/gitpod-protocol";
import { PaymentContext } from "../payment-context";
import { PageWithSettingsSubMenu } from "./PageWithSettingsSubMenu";
import { UserContext } from "../user-context";
import { BillingMode } from "@gitpod/gitpod-protocol/lib/billing-mode";
import { FeatureFlagContext } from "../contexts/FeatureFlagContext";

export default function Teams() {
return (
Expand All @@ -45,7 +44,7 @@ interface Slot extends TeamSubscriptionSlotResolved {
}

function AllTeams() {
const { userBillingMode } = useContext(UserContext);
const { isUsageBasedBillingEnabled } = useContext(FeatureFlagContext);
const { currency, isStudent, isChargebeeCustomer, setIsChargebeeCustomer } = useContext(PaymentContext);

const [slots, setSlots] = useState<Slot[]>([]);
Expand Down Expand Up @@ -453,8 +452,20 @@ function AllTeams() {
return pendingSlotsPurchase && pendingSlotsPurchase.tsId === ts.id;
};

const renderTeams = () => (
<React.Fragment>
return (
<div>
{isUsageBasedBillingEnabled && (
<Alert type="message" className="mb-4">
Your account has been enabled for usage-based billing. Discover faster workspace classes and only
pay for what you actually use.{" "}
<a className="gp-link" href="https://www.gitpod.io/docs/configure/billing/usage-based-billing">
Learn more
</a>
<br />
<br />
The old monthly plans are deprecated and will be cancelled by End of March 2023.
</Alert>
)}
<div className="flex flex-row">
<div className="flex-grow ">
<h3 className="self-center">All Team Plans</h3>
Expand All @@ -469,7 +480,11 @@ function AllTeams() {
{getActiveSubs().length > 0 && (
<button
className="self-end my-auto"
disabled={!!pendingPlanPurchase || getAvailableSubTypes().length === 0}
disabled={
!!isUsageBasedBillingEnabled ||
!!pendingPlanPurchase ||
getAvailableSubTypes().length === 0
}
onClick={() => showCreateTeamModal()}
>
Create Team Plan
Expand Down Expand Up @@ -501,7 +516,7 @@ function AllTeams() {
<AddMembersModal onClose={() => setAddMembersModal(undefined)} onBuy={onBuy} {...addMembersModal} />
)}

{getActiveSubs().length === 0 && !pendingPlanPurchase && (
{getActiveSubs().length === 0 && !pendingPlanPurchase && !isUsageBasedBillingEnabled && (
<div className="w-full flex h-80 mt-2 rounded-xl bg-gray-100 dark:bg-gray-900">
<div className="m-auto text-center">
<h3 className="self-center text-gray-500 dark:text-gray-400 mb-4">No Active Team Plans</h3>
Expand Down Expand Up @@ -602,23 +617,6 @@ function AllTeams() {
))}
</div>
)}
</React.Fragment>
);

// TOOD(gpl) We might want to reduce visibility of those actions similarly to how we guard access to that API, cmp.: https://github.com/gitpod-io/gitpod/blob/db90aefb9f7dc1d062e9cb73241c1fda2eccee4b/components/server/ee/src/workspace/gitpod-server-impl.ts#L2011
const showTeamPlans = BillingMode.showTeamSubscriptionUI(userBillingMode);
return (
<div>
{showTeamPlans ? (
renderTeams()
) : (
<div className="flex flex-row">
<div className="flex-grow ">
<h3 className="self-center">All Team Plans</h3>
<h2>Manage team plans and team members.</h2>
</div>
</div>
)}
</div>
);
}
Expand Down