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
180 changes: 90 additions & 90 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,101 +631,99 @@ export default function () {
);
}

const showPlans = userBillingMode && userBillingMode.mode === "chargebee";
return (
<div>
<PageWithSettingsSubMenu title="Plans" subtitle="Manage account usage and billing.">
{showPlans && (
<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>{" "}
plan.
{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>{" "}
plan.
</p>
{!isUsageBasedBillingEnabled && !assignedTs && (
<p className="text-base w-96 m-auto">
Upgrade your plan to get more hours and more parallel workspaces.
</p>
{!assignedTs && (
<p className="text-base w-96 m-auto">
Upgrade your plan to get more hours and more parallel workspaces.
</p>
)}
<Tooltip
content={`Current billing cycle: ${guessCurrentBillingCycle(currentPlan, accountStatement)
.map((d) => d.toLocaleDateString())
.join(" - ")}`}
)}
<Tooltip
content={`Current billing cycle: ${guessCurrentBillingCycle(currentPlan, accountStatement)
.map((d) => d.toLocaleDateString())
.join(" - ")}`}
>
<p className="mt-2 font-semibold text-gray-500">
Remaining hours:{" "}
{typeof accountStatement?.remainingHours === "number"
? Math.floor(accountStatement.remainingHours * 10) / 10
: accountStatement?.remainingHours}
</p>
</Tooltip>
{typeof accountStatement?.remainingHours === "number" &&
typeof currentPlan.hoursPerMonth === "number" ? (
<progress
value={currentPlan.hoursPerMonth - accountStatement.remainingHours}
max={currentPlan.hoursPerMonth}
/>
) : (
<progress value="0" max="100" />
)}
<p className="text-sm">
<a
className={`gp-link ${isChargebeeCustomer ? "" : "invisible"}`}
href="javascript:void(0)"
onClick={() => {
ChargebeeClient.getOrCreate().then((chargebeeClient) => chargebeeClient.openPortal());
}}
>
<p className="mt-2 font-semibold text-gray-500">
Remaining hours:{" "}
{typeof accountStatement?.remainingHours === "number"
? Math.floor(accountStatement.remainingHours * 10) / 10
: accountStatement?.remainingHours}
</p>
</Tooltip>
{typeof accountStatement?.remainingHours === "number" &&
typeof currentPlan.hoursPerMonth === "number" ? (
<progress
value={currentPlan.hoursPerMonth - accountStatement.remainingHours}
max={currentPlan.hoursPerMonth}
/>
) : (
<progress value="0" max="100" />
Billing
</a>
{!!accountStatement && Plans.isFreePlan(currentPlan.chargebeeId) && (
<span className="pl-6">
{currency === "EUR" ? (
<>
€ /{" "}
<a
className="text-blue-light hover:underline"
href="javascript:void(0)"
onClick={() => setCurrency("USD")}
>
$
</a>
</>
) : (
<>
<a
className="text-blue-light hover:underline"
href="javascript:void(0)"
onClick={() => setCurrency("EUR")}
>
</a>{" "}
/ $
</>
)}
</span>
)}
<p className="text-sm">
<a
className={`gp-link ${isChargebeeCustomer ? "" : "invisible"}`}
href="javascript:void(0)"
onClick={() => {
ChargebeeClient.getOrCreate().then((chargebeeClient) =>
chargebeeClient.openPortal(),
);
}}
>
Billing
</a>
{!!accountStatement && Plans.isFreePlan(currentPlan.chargebeeId) && (
<span className="pl-6">
{currency === "EUR" ? (
<>
€ /{" "}
<a
className="text-blue-light hover:underline"
href="javascript:void(0)"
onClick={() => setCurrency("USD")}
>
$
</a>
</>
) : (
<>
<a
className="text-blue-light hover:underline"
href="javascript:void(0)"
onClick={() => setCurrency("EUR")}
>
</a>{" "}
/ $
</>
)}
</span>
)}
</p>
</div>
)}
</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
Loading