Data for the plans page
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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) => <BreadcrumbLink to={match.pathname} title="Plans" />,
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
const { plans } = useTypedLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Callout variant={"pricing"}>
|
||||
|
||||
@@ -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() {
|
||||
)}
|
||||
</PageInfoGroup>
|
||||
</PageInfoRow>
|
||||
<PageTabs
|
||||
tabs={[
|
||||
{
|
||||
label: "Usage & Billing",
|
||||
to: UsagePath(organization),
|
||||
},
|
||||
{
|
||||
label: "Plans",
|
||||
to: PlansPath(organization),
|
||||
},
|
||||
]}
|
||||
layoutId="usage-and-billing"
|
||||
/>
|
||||
{isManagedCloud && (
|
||||
<PageTabs
|
||||
tabs={[
|
||||
{
|
||||
label: "Usage & Billing",
|
||||
to: UsagePath(organization),
|
||||
},
|
||||
{
|
||||
label: "Plans",
|
||||
to: PlansPath(organization),
|
||||
},
|
||||
]}
|
||||
layoutId="usage-and-billing"
|
||||
/>
|
||||
)}
|
||||
</PageHeader>
|
||||
<PageBody scrollable={false}>
|
||||
<div className="h-full overflow-y-auto p-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700">
|
||||
|
||||
Reference in New Issue
Block a user