hts/apps/migrant/app/[locale]/pricing/discover.tsx

210 lines
7.8 KiB
TypeScript

"use client";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import * as SliderPrimitive from "@radix-ui/react-slider";
import { HelpCircle, KeySquare, ListChecks } from "lucide-react";
import React, { useState } from "react";
import { SectionTitle } from "../../section-title";
import {
Bullet,
Color,
Cost,
FreeCardHighlight,
PricingCard,
PricingCardContent,
PricingCardFooter,
PricingCardHeader,
Separator,
} from "./components";
import { SubDiscoverySvg } from "./svgs";
const activeKeysSteps = [250, 1_000, 2_000, 5_000, 10_000, 50_000, 100_000, null];
const verificationsSteps = [
150_000,
250_000,
500_000,
1_000_000,
10_000_000,
100_000_000,
1_000_000_000,
null,
];
export const Discover: React.FC = () => {
const [activeKeysIndex, setActiveKeysIndex] = useState(0);
const activeKeys = activeKeysSteps[activeKeysIndex];
const billableKeys = Math.max(0, (activeKeys ?? 0) - 250);
const activeKeysCost = billableKeys * 0.1;
const activeKeysQuantityDisplay = fmtNumber(activeKeys ?? Number.POSITIVE_INFINITY);
const activeKeysCostDisplay = activeKeys === null ? "Custom" : fmtDollar(activeKeysCost);
const [verificationsIndex, setVerificationsIndex] = useState(0);
const verifications = verificationsSteps[verificationsIndex];
const billableVerifications = Math.max(0, (verifications ?? 0) - 150_000);
const verificationsCost = (billableVerifications * 10) / 100_000;
const verificationsQuantityDisplay = fmtNumber(verifications ?? Number.POSITIVE_INFINITY);
const verificationsCostDisplay = verifications === null ? "Custom" : fmtDollar(verificationsCost);
const totalCostDisplay =
verifications === null || activeKeys === null
? "Custom"
: fmtDollar(25 + activeKeysCost + verificationsCost);
return (
<div>
<SectionTitle
align="center"
title={
<>
Discover your pricing. <br /> Pay only what matters to you
</>
}
text={
<>
Find out exactly hwat your investment will be on Unkey, with our estimated cost
calculator.
<br />
Explore the cost per active key and key verifications.
</>
}
/>
<PricingCard color={Color.White} className="relative max-w-4xl mx-auto mt-20">
<FreeCardHighlight className="absolute top-0 right-0" />
<TooltipProvider delayDuration={10}>
<PricingCardHeader
title="Estimated cost calculator"
description="Find out how much you will pay by using Unkey"
withIcon={false}
color={Color.Purple}
className="bg-gradient-to-tr from-transparent to-[#ffffff]/10 "
/>
<Separator />
<PricingCardContent>
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-4">
<Cost dollar={totalCostDisplay} />
<Tooltip>
<TooltipTrigger asChild>
<HelpCircle className="w-4 h-4 text-white/40" style={{ strokeWidth: "1px" }} />
</TooltipTrigger>
<TooltipContent className="bg-black">
<p className="text-sm text-white/40">Cost break down:</p>
<div className="grid grid-cols-2 mt-4 gap-x-4 gap-y-2">
<span className="text-white">{fmtDollar(25)}</span>
<span className="text-sm text-white/40">Base Plan</span>
<span className="text-white">{activeKeysCostDisplay}</span>
<span className="text-sm text-white/40">Active Keys</span>
<span className="text-white">{verificationsCostDisplay}</span>
<span className="text-sm text-white/40">Verifications</span>
</div>
</TooltipContent>
</Tooltip>
</div>
<p className="text-sm text-white/40">Resources are summed and billed monthly</p>
</div>
<div className="flex flex-col gap-8">
<Row
label={<Bullet Icon={KeySquare} label="Active keys" color={Color.Purple} />}
slider={
<Slider
min={0}
max={activeKeysSteps.length - 1}
value={[activeKeysIndex]}
className=""
onValueChange={([v]) => setActiveKeysIndex(v)}
/>
}
quantity={
<div className="flex items-center gap-2">
<span className="text-white">{activeKeysQuantityDisplay}</span>
<span className="text-sm text-white/40">Keys</span>
</div>
}
cost={<PriceTag dollar={activeKeysCostDisplay} />}
/>
<Row
label={<Bullet Icon={ListChecks} label="Verifications" color={Color.Purple} />}
slider={
<Slider
min={0}
max={verificationsSteps.length - 1}
value={[verificationsIndex]}
className=""
onValueChange={([v]) => setVerificationsIndex(v)}
/>
}
quantity={
<div className="flex items-center gap-2">
<span className="text-white">{verificationsQuantityDisplay}</span>
<span className="text-sm text-white/40">Verifications</span>
</div>
}
cost={<PriceTag dollar={verificationsCostDisplay} />}
/>
</div>
</PricingCardContent>
<PricingCardFooter />
</TooltipProvider>
</PricingCard>{" "}
<SubDiscoverySvg className="container max-w-4xl mx-auto" />
</div>
);
};
const Row: React.FC<{
label: React.ReactNode;
slider: React.ReactNode;
quantity: React.ReactNode;
cost: React.ReactNode;
}> = ({ label, slider, quantity, cost }) => {
return (
<div className="flex items-center justify-between gap-4">
<div className="w-2/12">{label}</div>
<div className="w-6/12">{slider}</div>
<div className="w-2/12">{quantity}</div>
<div className="w-2/12">{cost}</div>
</div>
);
};
function fmtNumber(n: number): string {
return Intl.NumberFormat(undefined, { notation: "compact" }).format(n);
}
function fmtDollar(n: number): string {
return Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(n);
}
const PriceTag: React.FC<{ dollar: string }> = ({ dollar }) => {
return (
<div className="flex justify-end w-full">
<span className="h-6 px-2 text-sm font-semibold text-white rounded bg-white/10">
{dollar}
</span>
</div>
);
};
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn("relative flex w-full touch-none select-none items-center", className)}
{...props}
>
<SliderPrimitive.Track className="relative w-full h-px overflow-hidden rounded-full bg-gradient-to-r from-white/20 to-white/60 grow">
<SliderPrimitive.Range className="absolute h-full bg-gradient-to-r from-[#02DEFC] via-[#0239FC] to-[#7002FC]" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block w-4 h-4 transition-colors bg-white border-2 border-white rounded-full drop-shadow-[0_0_5px_rgba(255,255,255,1)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;