Pricing slider working
This commit is contained in:
@@ -1,28 +1,57 @@
|
||||
import * as Slider from "@radix-ui/react-slider";
|
||||
import { Plans } from "@trigger.dev/billing";
|
||||
import { useCallback, useState } from "react";
|
||||
import { DefinitionTip } from "../DefinitionTooltip";
|
||||
import { Header2 } from "../primitives/Headers";
|
||||
import { Paragraph } from "../primitives/Paragraph";
|
||||
import { DefinitionTip } from "../DefinitionTooltip";
|
||||
import { formatNumberCompact } from "~/utils/numberFormatter";
|
||||
|
||||
export function PricingCalculator({ plans }: { plans: Plans }) {
|
||||
const [selectedConcurrencyIndex, setSelectedConcurrencyIndex] = useState(0);
|
||||
const concurrentRunTiers = [
|
||||
{ code: "free", upto: plans.free.concurrentRuns?.freeAllowance! },
|
||||
...(plans.paid.concurrentRuns?.pricing?.tiers ?? []),
|
||||
];
|
||||
const [runs, setRuns] = useState(0);
|
||||
const runBrackets = [
|
||||
...(plans.paid.runs?.pricing?.brackets.map((b, index, arr) => ({
|
||||
unitCost: b.unitCost,
|
||||
from: index === 0 ? 0 : arr[index - 1].upto! + 1,
|
||||
upto: b.upto ?? arr[index - 1].upto! * 10,
|
||||
})) ?? []),
|
||||
];
|
||||
|
||||
console.log("Paid", plans.paid);
|
||||
|
||||
export function PricingCalculator() {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<ConcurrentRunsSlider />
|
||||
<RunsSlider />
|
||||
<ConcurrentRunsSlider
|
||||
options={concurrentRunTiers}
|
||||
selectedIndex={selectedConcurrencyIndex}
|
||||
setSelectedIndex={setSelectedConcurrencyIndex}
|
||||
/>
|
||||
<RunsSlider brackets={runBrackets} runs={runs} setRuns={setRuns} />
|
||||
<GrandTotal />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const concurrentRuns = [
|
||||
{ value: 5, label: "Up to 5" },
|
||||
{ value: 20, label: "Up to 20" },
|
||||
{ value: 50, label: "Up to 50" },
|
||||
{ value: 100, label: "Up to 100" },
|
||||
];
|
||||
function ConcurrentRunsSlider({
|
||||
options,
|
||||
selectedIndex,
|
||||
setSelectedIndex,
|
||||
}: {
|
||||
options: {
|
||||
code: string;
|
||||
upto: number;
|
||||
}[];
|
||||
selectedIndex: number;
|
||||
setSelectedIndex: (index: number) => void;
|
||||
}) {
|
||||
const selectedOption = options[selectedIndex];
|
||||
|
||||
function ConcurrentRunsSlider() {
|
||||
return (
|
||||
<form>
|
||||
<div>
|
||||
<div className="flex">
|
||||
<div className="flex w-full flex-col">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -34,12 +63,13 @@ function ConcurrentRunsSlider() {
|
||||
Concurrent Runs
|
||||
</DefinitionTip>
|
||||
</Header2>
|
||||
<Header2>Up to 5</Header2>
|
||||
<Header2>Up to {selectedOption.upto}</Header2>
|
||||
</div>
|
||||
<Slider.Root
|
||||
className="relative mb-2 mt-4 flex h-5 w-full touch-none select-none items-center"
|
||||
defaultValue={[0]}
|
||||
max={concurrentRuns.length - 1}
|
||||
value={[selectedIndex]}
|
||||
onValueChange={(value) => setSelectedIndex(value[0])}
|
||||
max={options.length - 1}
|
||||
step={1}
|
||||
>
|
||||
<Slider.Track className="relative h-[8px] grow rounded-full bg-slate-850">
|
||||
@@ -51,11 +81,10 @@ function ConcurrentRunsSlider() {
|
||||
/>
|
||||
</Slider.Root>
|
||||
<div className="ml-1.5 flex w-[99.85%] items-center justify-between">
|
||||
{concurrentRuns.map((run, i) => {
|
||||
const concurrrentRunsLabels = Object.values(run)[0];
|
||||
{options.map((tier, i) => {
|
||||
return (
|
||||
<Paragraph variant="extra-small" className="text-slate-600" key={i}>
|
||||
{concurrrentRunsLabels}
|
||||
{tier.upto}
|
||||
</Paragraph>
|
||||
);
|
||||
})}
|
||||
@@ -67,24 +96,34 @@ function ConcurrentRunsSlider() {
|
||||
</div>
|
||||
</div>
|
||||
<hr className="mt-6 border-border" />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Runs = [
|
||||
{ value: 10_000, label: "10k" },
|
||||
{ value: 20_000, label: "20k" },
|
||||
{ value: 150_000, label: "150k" },
|
||||
{ value: 500_000, label: "500k" },
|
||||
{ value: 1_000_000, label: "1m" },
|
||||
{ value: 2_500_000, label: "2.5m" },
|
||||
{ value: 6_250_000, label: "6.25m" },
|
||||
{ value: 6_250_001, label: "6.25m+" },
|
||||
];
|
||||
const runIncrements = 10_000;
|
||||
function RunsSlider({
|
||||
brackets,
|
||||
runs,
|
||||
setRuns,
|
||||
}: {
|
||||
brackets: {
|
||||
from: number;
|
||||
upto: number;
|
||||
unitCost: number;
|
||||
}[];
|
||||
runs: number;
|
||||
setRuns: (value: number) => void;
|
||||
}) {
|
||||
const [value, setValue] = useState(0);
|
||||
|
||||
const updateRuns = useCallback((value: number) => {
|
||||
setValue(value);
|
||||
const r = calculateRuns(value / runIncrements, brackets);
|
||||
setRuns(r);
|
||||
}, []);
|
||||
|
||||
function RunsSlider() {
|
||||
return (
|
||||
<form>
|
||||
<div>
|
||||
<div className="flex">
|
||||
<div className="flex w-full flex-col">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -93,12 +132,13 @@ function RunsSlider() {
|
||||
Runs
|
||||
</DefinitionTip>
|
||||
</Header2>
|
||||
<Header2>10k</Header2>
|
||||
<Header2>{formatNumberCompact(runs)}</Header2>
|
||||
</div>
|
||||
<Slider.Root
|
||||
className="relative mb-2 mt-4 flex h-5 w-full touch-none select-none items-center"
|
||||
defaultValue={[0]}
|
||||
max={Runs.length - 1}
|
||||
value={[value]}
|
||||
onValueChange={(value) => updateRuns(value[0])}
|
||||
max={runIncrements}
|
||||
step={1}
|
||||
>
|
||||
<Slider.Track className="relative h-[8px] grow rounded-full bg-slate-850">
|
||||
@@ -110,14 +150,16 @@ function RunsSlider() {
|
||||
/>
|
||||
</Slider.Root>
|
||||
<div className="flex w-[calc(100%+1rem)] items-center justify-between">
|
||||
{Runs.map((run, i) => {
|
||||
const RunsLabels = Object.values(run)[0];
|
||||
{brackets.map((bracket, i) => {
|
||||
return (
|
||||
<Paragraph variant="extra-small" className="text-slate-600" key={i}>
|
||||
{RunsLabels}
|
||||
{formatNumberCompact(bracket.from)}
|
||||
</Paragraph>
|
||||
);
|
||||
})}
|
||||
<Paragraph variant="extra-small" className="text-slate-600">
|
||||
{formatNumberCompact(brackets[brackets.length - 1].upto)}
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full items-start">
|
||||
@@ -126,10 +168,29 @@ function RunsSlider() {
|
||||
</div>
|
||||
</div>
|
||||
<hr className="mt-6 border-border" />
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function calculateRuns(percentage: number, brackets: { from: number; upto: number }[]) {
|
||||
//first we find which bucket we're in
|
||||
const buckets = brackets.length;
|
||||
const bucket = Math.min(Math.floor(percentage * buckets), brackets.length - 1);
|
||||
const percentagePerBucket = 1 / buckets;
|
||||
|
||||
//relevant bracket
|
||||
let bracket = brackets[bucket];
|
||||
const from = bracket.from;
|
||||
const upto = bracket.upto;
|
||||
|
||||
//how far as we into the bracket
|
||||
const percentageIntoBracket = (percentage - bucket * percentagePerBucket) / percentagePerBucket;
|
||||
|
||||
//calculate the runs
|
||||
const runs = Math.floor(from + (upto - from) * percentageIntoBracket);
|
||||
return runs;
|
||||
}
|
||||
|
||||
function GrandTotal() {
|
||||
return (
|
||||
<div className="flex justify-between">
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function Page() {
|
||||
<div>
|
||||
<Header2 spacing>Estimate your usage</Header2>
|
||||
<div className="flex h-full w-full rounded-md border border-border p-6">
|
||||
<PricingCalculator />
|
||||
<PricingCalculator plans={plans} />
|
||||
<div className="mx-6 min-h-full w-px bg-border" />
|
||||
<RunsVolumeDiscountTable brackets={plans.paid.runs?.pricing?.brackets ?? []} />
|
||||
</div>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"@tabler/icons-react": "^2.39.0",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@team-plain/typescript-sdk": "^3.5.0",
|
||||
"@trigger.dev/billing": "^1.0.8",
|
||||
"@trigger.dev/billing": "^1.0.9",
|
||||
"@trigger.dev/companyicons": "^1.5.32",
|
||||
"@trigger.dev/core": "workspace:*",
|
||||
"@trigger.dev/core-backend": "workspace:*",
|
||||
|
||||
Generated
+4
-4
@@ -130,7 +130,7 @@ importers:
|
||||
'@tailwindcss/typography': ^0.5.9
|
||||
'@team-plain/typescript-sdk': ^3.5.0
|
||||
'@total-typescript/ts-reset': ^0.4.2
|
||||
'@trigger.dev/billing': ^1.0.8
|
||||
'@trigger.dev/billing': ^1.0.9
|
||||
'@trigger.dev/companyicons': ^1.5.32
|
||||
'@trigger.dev/core': workspace:*
|
||||
'@trigger.dev/core-backend': workspace:*
|
||||
@@ -268,7 +268,7 @@ importers:
|
||||
'@tabler/icons-react': 2.40.0_react@18.2.0
|
||||
'@tailwindcss/container-queries': 0.1.1_tailwindcss@3.3.2
|
||||
'@team-plain/typescript-sdk': 3.5.0
|
||||
'@trigger.dev/billing': 1.0.8
|
||||
'@trigger.dev/billing': 1.0.9
|
||||
'@trigger.dev/companyicons': 1.5.32_biqbaboplfbrettd7655fr4n2y
|
||||
'@trigger.dev/core': link:../../packages/core
|
||||
'@trigger.dev/core-backend': link:../../packages/core-backend
|
||||
@@ -14333,8 +14333,8 @@ packages:
|
||||
resolution: {integrity: sha512-vqd7ZUDSrXFVT1n8b2kc3LnklncDQFPvR58yUS1kEP23/nHPAO9l1lMjUfnPrXYYk4Hj54rrLKMW5ipwk7k09A==}
|
||||
dev: true
|
||||
|
||||
/@trigger.dev/billing/1.0.8:
|
||||
resolution: {integrity: sha512-vqSsbqCHn7yAjjLY4kqRizEID6bZ0p73l5VoeSeplO+qDJGZDzL8aM104+Vqd3t2kCy7o+MtEwJ/cZNStKroKA==}
|
||||
/@trigger.dev/billing/1.0.9:
|
||||
resolution: {integrity: sha512-N7xxNz61FXHNKm5MJH5785XvE61HI2duHwera7Lf8hNOamkA82Ru+HcsX6gnnRJlXwJcxL913hR/7C5qHX+2vQ==}
|
||||
dependencies:
|
||||
zod: 3.22.3
|
||||
dev: false
|
||||
|
||||
Reference in New Issue
Block a user