Skip to content
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
6 changes: 6 additions & 0 deletions .server-changes/hide-self-serve-billing-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
area: webapp
type: improvement
---

Hide self-serve billing and upgrade options for directly-billed organizations; show Contact us instead.
27 changes: 20 additions & 7 deletions apps/webapp/app/components/BlankStatePanels.tsx
Comment thread
kathiekiwi marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ export function NoWaitpointTokens() {
);
}

export function BranchesNoBranchableEnvironment() {
export function BranchesNoBranchableEnvironment({ showSelfServe }: { showSelfServe: boolean }) {
const { isManagedCloud } = useFeatures();
const organization = useOrganization();

Expand Down Expand Up @@ -462,9 +462,16 @@ export function BranchesNoBranchableEnvironment() {
iconClassName="text-preview"
panelClassName="max-w-full"
accessory={
<LinkButton variant="primary/small" to={v3BillingPath(organization)}>
Upgrade
</LinkButton>
showSelfServe ? (
<LinkButton variant="primary/small" to={v3BillingPath(organization)}>
Upgrade
</LinkButton>
) : (
<Feedback
button={<Button variant="secondary/small">Request more</Button>}
defaultValue="enterprise"
/>
)
}
>
<Paragraph spacing variant="small">
Expand All @@ -483,10 +490,12 @@ export function BranchesNoBranches({
parentEnvironment,
limits,
canUpgrade,
showSelfServe,
}: {
parentEnvironment: { id: string };
limits: { used: number; limit: number };
canUpgrade: boolean;
showSelfServe: boolean;
}) {
const organization = useOrganization();

Expand All @@ -498,14 +507,18 @@ export function BranchesNoBranches({
iconClassName="text-preview"
panelClassName="max-w-full"
accessory={
canUpgrade ? (
showSelfServe && canUpgrade ? (
<LinkButton variant="primary/small" to={v3BillingPath(organization)}>
Upgrade
</LinkButton>
) : (
<Feedback
button={<Button variant="primary/small">Request more</Button>}
defaultValue="help"
button={
<Button variant={showSelfServe ? "primary/small" : "secondary/small"}>
Request more
</Button>
}
defaultValue={showSelfServe ? "help" : "enterprise"}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { LinkButton } from "../primitives/Buttons";
import { HelpAndFeedback } from "./HelpAndFeedbackPopover";
import { SideMenuHeader } from "./SideMenuHeader";
import { SideMenuItem } from "./SideMenuItem";
import { useShowSelfServe } from "~/hooks/useShowSelfServe";
import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route";
import { Paragraph } from "../primitives/Paragraph";
import { Badge } from "../primitives/Badge";
Expand All @@ -55,6 +56,7 @@ export function OrganizationSettingsSideMenu({
const { isManagedCloud } = useFeatures();
const featureFlags = useFeatureFlags();
const currentPlan = useCurrentPlan();
const showSelfServe = useShowSelfServe();
const isAdmin = useHasAdminAccess();
const showBuildInfo = isAdmin || !isManagedCloud;

Expand Down Expand Up @@ -103,14 +105,16 @@ export function OrganizationSettingsSideMenu({
) : undefined
}
/>
<SideMenuItem
name="Billing alerts"
icon={BellIcon}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
to={v3BillingAlertsPath(organization)}
data-action="billing-alerts"
/>
{showSelfServe ? (
<SideMenuItem
name="Billing alerts"
icon={BellIcon}
activeIconColor="text-text-bright"
inactiveIconColor="text-text-dimmed"
to={v3BillingAlertsPath(organization)}
data-action="billing-alerts"
/>
) : null}
</>
)}
<SideMenuItem
Expand Down
12 changes: 12 additions & 0 deletions apps/webapp/app/components/schedules/PurchaseSchedulesModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { EnvelopeIcon } from "@heroicons/react/20/solid";
import { DialogClose } from "@radix-ui/react-dialog";
import { useFetcher } from "@remix-run/react";
import { type ReactNode, useEffect, useState } from "react";
import { Feedback } from "~/components/Feedback";
import { Button } from "~/components/primitives/Buttons";
import {
Dialog,
Expand All @@ -20,6 +21,7 @@ import { InputNumberStepper } from "~/components/primitives/InputNumberStepper";
import { Label } from "~/components/primitives/Label";
import { Paragraph } from "~/components/primitives/Paragraph";
import { SpinnerWhite } from "~/components/primitives/Spinner";
import { useShowSelfServe } from "~/hooks/useShowSelfServe";
import { PurchaseSchema } from "~/routes/resources.orgs.$organizationSlug.schedules-addon";
import { cn } from "~/utils/cn";
import { formatCurrency, formatNumber } from "~/utils/numberFormatter";
Expand Down Expand Up @@ -49,6 +51,7 @@ export function PurchaseSchedulesModal({
planScheduleLimit,
triggerButton,
}: Props) {
const showSelfServe = useShowSelfServe();
const fetcher = useFetcher();
const lastSubmission =
fetcher.data && typeof fetcher.data === "object" && "intent" in fetcher.data
Expand Down Expand Up @@ -105,6 +108,15 @@ export function PurchaseSchedulesModal({
const stepUnit = formatNumber(stepSize);
const title = extraSchedules === 0 ? "Purchase extra schedules…" : "Add/remove extra schedules…";

if (!showSelfServe) {
return (
<Feedback
defaultValue="enterprise"
button={<Button variant="secondary/small">Request more</Button>}
/>
);
}

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
Expand Down
83 changes: 83 additions & 0 deletions apps/webapp/app/components/schedules/ScheduleLimitActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { ArrowUpCircleIcon } from "@heroicons/react/20/solid";
import { Feedback } from "~/components/Feedback";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { useShowSelfServe } from "~/hooks/useShowSelfServe";
import { type MatchedOrganization } from "~/hooks/useOrganizations";
import { v3BillingPath } from "~/utils/pathBuilder";
import { PurchaseSchedulesModal, type SchedulePricing } from "./PurchaseSchedulesModal";

type Props = {
actionPath: string;
canPurchaseSchedules: boolean;
schedulePricing: SchedulePricing | null;
extraSchedules: number;
limits: { used: number; limit: number };
maxScheduleQuota: number;
planScheduleLimit: number;
canUpgrade: boolean;
organization: MatchedOrganization;
variant?: "dialog" | "banner";
};

export function ScheduleLimitActions({
actionPath,
canPurchaseSchedules,
schedulePricing,
extraSchedules,
limits,
maxScheduleQuota,
planScheduleLimit,
canUpgrade,
organization,
variant = "banner",
}: Props) {
const showSelfServe = useShowSelfServe();

if (!showSelfServe) {
return (
<Feedback
button={<Button variant="secondary/small">Request more</Button>}
defaultValue="enterprise"
/>
);
}

if (canPurchaseSchedules && schedulePricing) {
return (
<PurchaseSchedulesModal
actionPath={actionPath}
schedulePricing={schedulePricing}
extraSchedules={extraSchedules}
usedSchedules={limits.used}
maxQuota={maxScheduleQuota}
planScheduleLimit={planScheduleLimit}
triggerButton={
variant === "dialog" ? (
<Button variant="primary/small">Purchase more…</Button>
) : undefined
}
/>
);
}

if (canUpgrade) {
return variant === "dialog" ? (
<LinkButton variant="primary/small" to={v3BillingPath(organization)}>
Upgrade
</LinkButton>
) : (
<LinkButton
to={v3BillingPath(organization)}
variant="secondary/small"
LeadingIcon={ArrowUpCircleIcon}
leadingIconClassName="text-indigo-500"
>
Upgrade
</LinkButton>
);
}

return (
<Feedback button={<Button variant="primary/small">Request more</Button>} defaultValue="help" />
);
}
46 changes: 14 additions & 32 deletions apps/webapp/app/components/schedules/SchedulesUsageBar.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { ArrowUpCircleIcon } from "@heroicons/react/20/solid";
import { Feedback } from "~/components/Feedback";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Header3 } from "~/components/primitives/Headers";
import { InfoIconTooltip, SimpleTooltip } from "~/components/primitives/Tooltip";
import { useOrganization } from "~/hooks/useOrganizations";
import { v3BillingPath, v3SchedulesAddOnPath } from "~/utils/pathBuilder";
import {
PurchaseSchedulesModal,
type SchedulePricing,
} from "./PurchaseSchedulesModal";
import { v3SchedulesAddOnPath } from "~/utils/pathBuilder";
import { ScheduleLimitActions } from "./ScheduleLimitActions";
import { type SchedulePricing } from "./PurchaseSchedulesModal";

type Props = {
limits: { used: number; limit: number };
Expand Down Expand Up @@ -81,30 +76,17 @@ export function SchedulesUsageBar({
</div>
)}

{canPurchaseSchedules && schedulePricing ? (
<PurchaseSchedulesModal
actionPath={actionPath}
schedulePricing={schedulePricing}
extraSchedules={extraSchedules}
usedSchedules={limits.used}
maxQuota={maxScheduleQuota}
planScheduleLimit={planScheduleLimit}
/>
) : canUpgrade ? (
<LinkButton
to={v3BillingPath(organization)}
variant="secondary/small"
LeadingIcon={ArrowUpCircleIcon}
leadingIconClassName="text-indigo-500"
>
Upgrade
</LinkButton>
) : (
<Feedback
button={<Button variant="secondary/small">Request more</Button>}
defaultValue="help"
/>
)}
<ScheduleLimitActions
actionPath={actionPath}
canPurchaseSchedules={canPurchaseSchedules}
schedulePricing={schedulePricing}
extraSchedules={extraSchedules}
limits={limits}
maxScheduleQuota={maxScheduleQuota}
planScheduleLimit={planScheduleLimit}
canUpgrade={canUpgrade}
organization={organization}
/>
</div>
</div>
</div>
Expand Down
7 changes: 7 additions & 0 deletions apps/webapp/app/hooks/useShowSelfServe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route";

/** Whether the org should see self-serve billing UI (plan picker, Stripe checkout, upgrades). */
export function useShowSelfServe(): boolean {
const plan = useCurrentPlan();
return plan?.v3Subscription?.showSelfServe ?? true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import assertNever from "assert-never";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { AlertsNoneDev, AlertsNoneDeployed } from "~/components/BlankStatePanels";
import { Feedback } from "~/components/Feedback";
import { EnvironmentCombo } from "~/components/environments/EnvironmentLabel";
import { MainCenteredContainer, PageBody, PageContainer } from "~/components/layout/AppLayout";
import { Button, LinkButton } from "~/components/primitives/Buttons";
Expand Down Expand Up @@ -45,6 +46,7 @@ import {
import { EnabledStatus } from "~/components/runs/v3/EnabledStatus";
import { prisma } from "~/db.server";
import { useEnvironment } from "~/hooks/useEnvironment";
import { useShowSelfServe } from "~/hooks/useShowSelfServe";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { redirectWithSuccessMessage } from "~/models/message.server";
Expand Down Expand Up @@ -182,6 +184,7 @@ export default function Page() {
const organization = useOrganization();
const project = useProject();
const environment = useEnvironment();
const showSelfServe = useShowSelfServe();

const requiresUpgrade = limits.used >= limits.limit;

Expand Down Expand Up @@ -343,9 +346,16 @@ export default function Page() {
</Header3>
)}

<LinkButton to={v3BillingPath(organization)} variant="secondary/small">
Upgrade
</LinkButton>
{showSelfServe ? (
<LinkButton to={v3BillingPath(organization)} variant="secondary/small">
Upgrade
</LinkButton>
) : (
<Feedback
defaultValue="enterprise"
button={<Button variant="secondary/small">Request more</Button>}
/>
)}
</div>
</div>
</div>
Expand Down
Loading