Improved some of the display

This commit is contained in:
Matt Aitken
2023-12-01 19:02:13 +00:00
parent ed2ef7d77c
commit 8db4d9d2ee
2 changed files with 139 additions and 70 deletions
@@ -56,74 +56,119 @@ export function PricingTiers({
className
)}
>
<TierFree plan={plans.free} />
<TierFree plan={plans.free} organizationSlug={organizationSlug} showActionText={true} />
<TierPro plan={plans.paid} organizationSlug={organizationSlug} showActionText={true} />
<TierEnterprise />
</div>
);
}
export function TierFree({ plan }: { plan: Plan }) {
export function TierFree({
plan,
organizationSlug,
showActionText,
}: {
plan: Plan;
organizationSlug: string;
showActionText: boolean;
}) {
const lastSubmission = useActionData();
const [form] = useForm({
id: "subscribe",
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema: SetPlanBodySchema });
},
});
const navigation = useNavigation();
const isLoading = navigation.state === "submitting" || navigation.state === "loading";
const currentPlan = useCurrentPlan();
const isCurrentPlan =
currentPlan?.subscription?.isPaying === undefined ||
currentPlan?.subscription?.isPaying === false;
let actionText = "Select plan";
if (showActionText) {
if (isCurrentPlan) {
actionText = "Current Plan";
} else {
actionText = "Downgrade";
}
}
return (
<TierContainer>
<Header title={plan.title} cost={0} />
<TierLimit>
Up to {plan.concurrentRuns?.freeAllowance}{" "}
<DefinitionTip
title={pricingDefinitions.concurrentRuns.title}
content={pricingDefinitions.concurrentRuns.content}
<Form action={`/resources/${organizationSlug}/subscribe`} method="post" {...form.props}>
<Header title={plan.title} cost={0} />
<TierLimit>
Up to {plan.concurrentRuns?.freeAllowance}{" "}
<DefinitionTip
title={pricingDefinitions.concurrentRuns.title}
content={pricingDefinitions.concurrentRuns.content}
>
{pricingDefinitions.concurrentRuns.title}
</DefinitionTip>
</TierLimit>
<input type="hidden" name="type" value="free" />
<Button
variant="secondary/large"
fullWidth
className="text-md my-6 font-medium"
disabled={isLoading || isCurrentPlan}
LeadingIcon={isLoading ? "spinner-white" : undefined}
>
{pricingDefinitions.concurrentRuns.title}
</DefinitionTip>
</TierLimit>
<Button variant="secondary/large" fullWidth className="text-md my-6 font-medium">
Choose Plan
</Button>
<ul className="flex flex-col gap-2.5">
<FeatureItem checked>
Up to {plan.runs?.freeAllowance ? formatNumberCompact(plan.runs.freeAllowance) : ""}{" "}
<DefinitionTip
title={pricingDefinitions.jobRuns.title}
content={pricingDefinitions.jobRuns.content}
>
{pricingDefinitions.jobRuns.title}
</DefinitionTip>
</FeatureItem>
<FeatureItem checked>
Unlimited{" "}
<DefinitionTip
title={pricingDefinitions.jobs.title}
content={pricingDefinitions.jobs.content}
>
Jobs
</DefinitionTip>
</FeatureItem>
<FeatureItem checked>
Unlimited{" "}
<DefinitionTip
title={pricingDefinitions.tasks.title}
content={pricingDefinitions.tasks.content}
>
Tasks
</DefinitionTip>
</FeatureItem>
<FeatureItem checked>
Unlimited{" "}
<DefinitionTip
title={pricingDefinitions.events.title}
content={pricingDefinitions.events.content}
>
Events
</DefinitionTip>
</FeatureItem>
<FeatureItem checked>Unlimited team members</FeatureItem>
<FeatureItem checked>24 hour log retention</FeatureItem>
<FeatureItem checked>Community support</FeatureItem>
<FeatureItem>Custom Integrations</FeatureItem>
<FeatureItem>Role-based access control</FeatureItem>
<FeatureItem>SSO</FeatureItem>
<FeatureItem>On-prem option</FeatureItem>
</ul>
{isLoading ? "Updating plan" : actionText}
</Button>
<ul className="flex flex-col gap-2.5">
<FeatureItem checked>
Up to {plan.runs?.freeAllowance ? formatNumberCompact(plan.runs.freeAllowance) : ""}{" "}
<DefinitionTip
title={pricingDefinitions.jobRuns.title}
content={pricingDefinitions.jobRuns.content}
>
{pricingDefinitions.jobRuns.title}
</DefinitionTip>
</FeatureItem>
<FeatureItem checked>
Unlimited{" "}
<DefinitionTip
title={pricingDefinitions.jobs.title}
content={pricingDefinitions.jobs.content}
>
Jobs
</DefinitionTip>
</FeatureItem>
<FeatureItem checked>
Unlimited{" "}
<DefinitionTip
title={pricingDefinitions.tasks.title}
content={pricingDefinitions.tasks.content}
>
Tasks
</DefinitionTip>
</FeatureItem>
<FeatureItem checked>
Unlimited{" "}
<DefinitionTip
title={pricingDefinitions.events.title}
content={pricingDefinitions.events.content}
>
Events
</DefinitionTip>
</FeatureItem>
<FeatureItem checked>Unlimited team members</FeatureItem>
<FeatureItem checked>24 hour log retention</FeatureItem>
<FeatureItem checked>Community support</FeatureItem>
<FeatureItem>Custom Integrations</FeatureItem>
<FeatureItem>Role-based access control</FeatureItem>
<FeatureItem>SSO</FeatureItem>
<FeatureItem>On-prem option</FeatureItem>
</ul>
</Form>
</TierContainer>
);
}
@@ -138,7 +183,7 @@ export function TierPro({
showActionText: boolean;
}) {
const lastSubmission = useActionData();
const [form, { path, feedbackType, message }] = useForm({
const [form] = useForm({
id: "subscribe",
// TODO: type this
lastSubmission: lastSubmission as any,
@@ -164,12 +209,16 @@ export function TierPro({
const isCurrentPlan = currentConcurrencyTier === concurrentBracketCode;
let actionText = isCurrentPlan ? "Current Plan" : "Select plan";
let actionText = "Select plan";
if (showActionText && !isCurrentPlan) {
const currentTierIndex = concurrencyTiers.findIndex((c) => c.code === currentConcurrencyTier);
const selectedTierIndex = concurrencyTiers.findIndex((c) => c.code === concurrentBracketCode);
actionText = currentTierIndex < selectedTierIndex ? "Upgrade" : "Downgrade";
if (showActionText) {
if (isCurrentPlan) {
actionText = "Current Plan";
} else {
const currentTierIndex = concurrencyTiers.findIndex((c) => c.code === currentConcurrencyTier);
const selectedTierIndex = concurrencyTiers.findIndex((c) => c.code === concurrentBracketCode);
actionText = currentTierIndex < selectedTierIndex ? "Upgrade" : "Downgrade";
}
}
return (
@@ -27,6 +27,7 @@ import { formatDurationInDays } from "~/utils";
import { Handle } from "~/utils/handle";
import { OrganizationParamsSchema, plansPath, usagePath } from "~/utils/pathBuilder";
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
import { ActiveSubscription } from "@trigger.dev/billing";
export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
@@ -51,6 +52,29 @@ export const handle: Handle = {
breadcrumb: (match) => <BreadcrumbLink to={match.pathname} title="Usage & Billing" />,
};
function planLabel(subscription?: ActiveSubscription) {
if (!subscription) {
return "You're currently on the Free plan";
}
if (!subscription.isPaying) {
return `You're currently on the ${subscription.plan.title} plan`;
}
const costDescription = subscription.plan.concurrentRuns.pricing
? `\$${subscription.plan.concurrentRuns.pricing?.tierCost}/mo`
: "";
if (subscription.canceledAt) {
return (
<>
You're on the {costDescription} {subscription.plan.title} plan until{" "}
<DateTime includeTime={false} date={subscription.canceledAt} /> when you'll be on the Free
plan
</>
);
}
return `You're currently on the ${costDescription} ${subscription.plan.title} plan`;
}
export default function Page() {
const { stripePortalLink } = useTypedLoaderData<typeof loader>();
const organization = useOrganization();
@@ -93,11 +117,7 @@ export default function Page() {
{currentPlan?.subscription && (
<PageInfoProperty
icon={<ReceiptRefundIcon className="h-4 w-4 text-green-600" />}
value={`You're currently on the${
currentPlan.subscription.plan.concurrentRuns.pricing?.tierCost
? ` \$${currentPlan.subscription.plan.concurrentRuns.pricing?.tierCost}/mo`
: ""
} ${currentPlan.subscription?.plan.title} plan`}
value={planLabel(currentPlan.subscription)}
/>
)}
{currentPlan?.subscription?.isPaying && (