Data for the plans page

This commit is contained in:
Matt Aitken
2023-12-01 16:07:08 +00:00
parent aed0ce666f
commit c1480cef8b
4 changed files with 75 additions and 18 deletions
@@ -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">