diff --git a/apps/webapp/app/presenters/BillingPresenter.server.ts b/apps/webapp/app/presenters/BillingPresenter.server.ts
index 4818facfd..3ebfa13f1 100644
--- a/apps/webapp/app/presenters/BillingPresenter.server.ts
+++ b/apps/webapp/app/presenters/BillingPresenter.server.ts
@@ -82,4 +82,19 @@ export class BillingPresenter {
return undefined;
}
}
+
+ async getPlans() {
+ if (!this.#billingClient) return undefined;
+ try {
+ const result = await this.#billingClient.plans();
+ if (!result.success) {
+ logger.error("Error getting plans", { error: result.error });
+ return undefined;
+ }
+ return result;
+ } catch (e) {
+ logger.error("Error getting plans", { error: e });
+ return undefined;
+ }
+ }
}
diff --git a/apps/webapp/app/presenters/OrgBillingPlanPresenter.ts b/apps/webapp/app/presenters/OrgBillingPlanPresenter.ts
new file mode 100644
index 000000000..d5525a33b
--- /dev/null
+++ b/apps/webapp/app/presenters/OrgBillingPlanPresenter.ts
@@ -0,0 +1,16 @@
+import { PrismaClient, prisma } from "~/db.server";
+import { logger } from "~/services/logger.server";
+import { BillingPresenter } from "./BillingPresenter.server";
+
+export class OrgBillingPlanPresenter {
+ #prismaClient: PrismaClient;
+
+ constructor(prismaClient: PrismaClient = prisma) {
+ this.#prismaClient = prismaClient;
+ }
+
+ public async call({ slug }: { slug: string }) {
+ const billingPresenter = new BillingPresenter(true);
+ return billingPresenter.getPlans();
+ }
+}
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing.plans/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing.plans/route.tsx
index b38cdd829..46fe5125e 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing.plans/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing.plans/route.tsx
@@ -1,16 +1,40 @@
+import { LoaderFunctionArgs } from "@remix-run/server-runtime";
+import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
import { PricingCalculator } from "~/components/billing/PricingCalculator";
import { PricingTiers, TierEnterprise, TierFree, TierPro } from "~/components/billing/PricingTiers";
import { RunsVolumeDiscountTable } from "~/components/billing/RunsVolumeDiscountTable";
import { BreadcrumbLink } from "~/components/navigation/Breadcrumb";
import { Callout } from "~/components/primitives/Callout";
import { Header2 } from "~/components/primitives/Headers";
+import { featuresForRequest } from "~/features.server";
+import { OrgBillingPlanPresenter } from "~/presenters/OrgBillingPlanPresenter";
import { Handle } from "~/utils/handle";
+import { OrganizationParamsSchema, organizationBillingPath } from "~/utils/pathBuilder";
+
+export async function loader({ params, request }: LoaderFunctionArgs) {
+ const { organizationSlug } = OrganizationParamsSchema.parse(params);
+
+ const { isManagedCloud } = featuresForRequest(request);
+ if (!isManagedCloud) {
+ return redirect(organizationBillingPath({ slug: organizationSlug }));
+ }
+
+ const presenter = new OrgBillingPlanPresenter();
+ const plans = await presenter.call({ slug: organizationSlug });
+ if (!plans) {
+ throw new Response(null, { status: 404 });
+ }
+
+ return typedjson({ plans });
+}
export const handle: Handle = {
breadcrumb: (match) => ,
};
export default function Page() {
+ const { plans } = useTypedLoaderData();
+
return (
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx
index 0b003998f..713009815 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.billing/route.tsx
@@ -6,6 +6,7 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
import { BreadcrumbLink } from "~/components/navigation/Breadcrumb";
import { LinkButton } from "~/components/primitives/Buttons";
+import { DateTime } from "~/components/primitives/DateTime";
import {
PageButtons,
PageHeader,
@@ -16,17 +17,16 @@ import {
PageTitle,
PageTitleRow,
} from "~/components/primitives/PageHeader";
+import { featuresForRequest } from "~/features.server";
+import { useFeatures } from "~/hooks/useFeatures";
import { useOrganization } from "~/hooks/useOrganizations";
+import { BillingPresenter } from "~/presenters/BillingPresenter.server";
import { OrgUsagePresenter } from "~/presenters/OrgUsagePresenter.server";
import { requireUserId } from "~/services/session.server";
+import { formatDurationMilliseconds } from "~/utils";
import { Handle } from "~/utils/handle";
import { OrganizationParamsSchema, PlansPath, UsagePath } from "~/utils/pathBuilder";
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
-import { useFeatures } from "~/hooks/useFeatures";
-import { DateTime } from "~/components/primitives/DateTime";
-import { formatDuration, formatDurationMilliseconds } from "~/utils";
-import { featuresForRequest } from "~/features.server";
-import { BillingPresenter } from "~/presenters/BillingPresenter.server";
export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
@@ -118,19 +118,21 @@ export default function Page() {
)}
-
+ {isManagedCloud && (
+
+ )}