Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 124 additions & 116 deletions mirascope-ui/blocks/lilypad-pricing.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import { Check, X } from "lucide-react";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/mirascope-ui/ui/tabs";
import { ButtonLink } from "@/mirascope-ui/ui/button-link";
import { cn } from "@/mirascope-ui/lib/utils";
import { ButtonLink } from "@/mirascope-ui/ui/button-link";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/mirascope-ui/ui/tabs";
import { Check, X } from "lucide-react";

// Feature row component for displaying features with the same value across tiers
const FeatureRow = ({
feature,
free,
pro,
team,
}: {
interface FeatureRowProps {
feature: string;
free: string | boolean;
pro: string | boolean;
team: string | boolean;
}) => {
}
// Feature row component for displaying features with the same value across tiers
const FeatureRow = ({ feature, free, pro, team }: FeatureRowProps) => {
// If all tiers have the exact same value (and it's not a boolean)
const allSameNonBoolean = free === pro && pro === team && typeof free === "string" && free !== "";

Expand Down Expand Up @@ -85,25 +81,28 @@ const FeatureRow = ({
);
};

interface PricingTierProps {
name: string;
price: string;
description: string;
buttonText?: string;
buttonLink?: string;
customButton?: React.ReactNode;
badge?: "Open Beta" | "Closed Beta";
variant?: "default" | "outline";
Comment thread
brenkao marked this conversation as resolved.
Outdated
}
// Pricing tier component
const PricingTier = ({
name,
price,
description,
buttonText,
buttonLink,
customButton,
badge,
variant = "default",
}: {
name: string;
price: string;
description: string;
buttonText: string;
buttonLink: string;
badge?: "Open Beta" | "Closed Beta";
variant?: "default" | "outline";
}) => (
<div className="bg-background border-border overflow-hidden rounded-lg border shadow-sm">
}: PricingTierProps) => (
<div className="border-border bg-background overflow-hidden rounded-lg border shadow-sm">
<div className={cn("bg-background px-6 py-8")}>
<div className="mb-2 flex items-center gap-2">
<h3 className={cn("text-foreground text-xl font-semibold")}>{name}</h3>
Expand All @@ -127,26 +126,19 @@ const PricingTier = ({
<span className="text-muted-foreground ml-1 text-sm">/ month</span>
)}
</div>
<ButtonLink href={buttonLink} className="w-full" variant={variant}>
{buttonText}
</ButtonLink>
{customButton ?? (
<ButtonLink href={buttonLink ?? "/"} className="w-full" variant={variant}>
Comment thread
brenkao marked this conversation as resolved.
Outdated
{buttonText}
</ButtonLink>
)}
</div>
</div>
);

// Feature comparison table component
const FeatureComparisonTable = ({
features,
}: {
features: Array<{
feature: string;
free: string | boolean;
pro: string | boolean;
team: string | boolean;
}>;
}) => (
<div className="bg-background border-border overflow-hidden rounded-lg border shadow-sm">
<div className="bg-accent border-border border-b px-4 py-5 sm:px-6">
export const FeatureComparisonTable = ({ features }: { features: FeatureRowProps[] }) => (
<div className="border-border bg-background overflow-hidden rounded-lg border shadow-sm">
<div className="border-border bg-accent border-b px-4 py-5 sm:px-6">
<h3 className="text-accent-foreground text-lg font-medium">Feature Comparison</h3>
</div>
<div className="bg-background overflow-x-auto px-4 py-5 sm:p-6">
Expand All @@ -160,22 +152,17 @@ const FeatureComparisonTable = ({

{/* Table rows */}
{features.map((feat, i) => (
<FeatureRow
key={i}
feature={feat.feature}
free={feat.free}
pro={feat.pro}
team={feat.team}
/>
<FeatureRow key={i} {...feat} />
))}
</div>
</div>
);

interface TierAction {
buttonText: string;
buttonLink: string;
buttonText?: string;
buttonLink?: string;
variant?: "default" | "outline";
customButton?: React.ReactNode;
}

interface PricingActions {
Expand All @@ -191,48 +178,47 @@ interface PricingActions {
};
}

// Cloud hosted features
export const cloudHostedFeatures = [
{ feature: "Projects", free: "Unlimited", pro: "Unlimited", team: "Unlimited" },
{ feature: "Users", free: "2", pro: "10", team: "Unlimited" },
{
feature: "Tracing",
free: "30k spans / month",
pro: "100k spans / month (thereafter $1 per 10k)",
team: "1M spans / month (thereafter $1 per 10k)",
},
{ feature: "Data Retention", free: "30 days", pro: "90 days", team: "180 days" },
{ feature: "Versioned Functions", free: true, pro: true, team: true },
{ feature: "Playground", free: true, pro: true, team: true },
{ feature: "Comparisons", free: true, pro: true, team: true },
{ feature: "Annotations", free: true, pro: true, team: true },
{ feature: "Support (Community)", free: true, pro: true, team: true },
{ feature: "Support (Chat / Email)", free: false, pro: true, team: true },
{ feature: "Support (Private Slack)", free: false, pro: false, team: true },
{ feature: "API Rate Limits", free: "10 / minute", pro: "100 / minute", team: "1000 / minute" },
];

// Self-hosted features
export const selfHostedFeatures = [
{ feature: "Projects", free: "Unlimited", pro: "Unlimited", team: "Unlimited" },
{ feature: "Users", free: "Unlimited", pro: "As licensed", team: "As licensed" },
{ feature: "Tracing", free: "No limits", pro: "No limits", team: "No limits" },
{ feature: "Data Retention", free: "No limits", pro: "No limits", team: "No limits" },
{ feature: "Versioned Functions", free: true, pro: true, team: true },
{ feature: "Playground", free: false, pro: true, team: true },
{ feature: "Comparisons", free: false, pro: true, team: true },
{ feature: "Annotations", free: false, pro: true, team: true },
{ feature: "Support (Community)", free: true, pro: true, team: true },
{ feature: "Support (Chat / Email)", free: false, pro: true, team: true },
{ feature: "Support (Private Slack)", free: false, pro: false, team: true },
{ feature: "API Rate Limits", free: "No limits", pro: "No limits", team: "No limits" },
];
interface LilypadPricingProps {
actions: PricingActions;
}

export function LilypadPricing({ actions }: LilypadPricingProps) {
// Cloud hosted features
const cloudHostedFeatures = [
{ feature: "Projects", free: "Unlimited", pro: "Unlimited", team: "Unlimited" },
{ feature: "Users", free: "2", pro: "10", team: "Unlimited" },
{
feature: "Tracing",
free: "30k spans / month",
pro: "100k spans / month (thereafter $1 per 10k)",
team: "1M spans / month (thereafter $1 per 10k)",
},
{ feature: "Data Retention", free: "30 days", pro: "90 days", team: "180 days" },
{ feature: "Versioned Functions", free: true, pro: true, team: true },
{ feature: "Playground", free: true, pro: true, team: true },
{ feature: "Comparisons", free: true, pro: true, team: true },
{ feature: "Annotations", free: true, pro: true, team: true },
{ feature: "Support (Community)", free: true, pro: true, team: true },
{ feature: "Support (Chat / Email)", free: false, pro: true, team: true },
{ feature: "Support (Private Slack)", free: false, pro: false, team: true },
{ feature: "API Rate Limits", free: "10 / minute", pro: "100 / minute", team: "1000 / minute" },
];

// Self-hosted features
const selfHostedFeatures = [
{ feature: "Projects", free: "Unlimited", pro: "Unlimited", team: "Unlimited" },
{ feature: "Users", free: "Unlimited", pro: "As licensed", team: "As licensed" },
{ feature: "Tracing", free: "No limits", pro: "No limits", team: "No limits" },
{ feature: "Data Retention", free: "No limits", pro: "No limits", team: "No limits" },
{ feature: "Versioned Functions", free: true, pro: true, team: true },
{ feature: "Playground", free: false, pro: true, team: true },
{ feature: "Comparisons", free: false, pro: true, team: true },
{ feature: "Annotations", free: false, pro: true, team: true },
{ feature: "Support (Community)", free: true, pro: true, team: true },
{ feature: "Support (Chat / Email)", free: false, pro: true, team: true },
{ feature: "Support (Private Slack)", free: false, pro: false, team: true },
{ feature: "API Rate Limits", free: "No limits", pro: "No limits", team: "No limits" },
];

return (
<div className="px-4 py-4">
<div className="mx-auto max-w-4xl">
Expand All @@ -256,35 +242,7 @@ export function LilypadPricing({ actions }: LilypadPricingProps) {

{/* Hosted By Us Tab Content */}
<TabsContent value="hosted">
<div className="mb-10 grid gap-8 md:grid-cols-3">
<PricingTier
name="Free"
price="$0"
description="For individuals just getting started"
buttonText={actions.hosted.free.buttonText}
buttonLink={actions.hosted.free.buttonLink}
badge="Open Beta"
variant={actions.hosted.free.variant}
/>
<PricingTier
name="Pro"
price="TBD"
description="For teams with more advanced needs"
buttonText={actions.hosted.pro.buttonText}
buttonLink={actions.hosted.pro.buttonLink}
badge="Closed Beta"
variant={actions.hosted.pro.variant || "outline"}
/>
<PricingTier
name="Team"
price="TBD"
description="For larger teams requiring dedicated support"
buttonText={actions.hosted.team.buttonText}
buttonLink={actions.hosted.team.buttonLink}
badge="Closed Beta"
variant={actions.hosted.team.variant || "outline"}
/>
</div>
<LilypadCloudPricing hostedActions={actions.hosted} />

{/* Feature comparison table */}
<FeatureComparisonTable features={cloudHostedFeatures} />
Expand All @@ -309,7 +267,7 @@ export function LilypadPricing({ actions }: LilypadPricingProps) {
buttonText={actions.selfHosted.pro.buttonText}
buttonLink={actions.selfHosted.pro.buttonLink}
badge="Closed Beta"
variant={actions.selfHosted.pro.variant || "outline"}
variant={actions.selfHosted.pro.variant ?? "outline"}
/>
<PricingTier
name="Team"
Expand All @@ -318,7 +276,7 @@ export function LilypadPricing({ actions }: LilypadPricingProps) {
buttonText={actions.selfHosted.team.buttonText}
buttonLink={actions.selfHosted.team.buttonLink}
badge="Closed Beta"
variant={actions.selfHosted.team.variant || "outline"}
variant={actions.selfHosted.team.variant ?? "outline"}
/>
</div>

Expand All @@ -328,7 +286,7 @@ export function LilypadPricing({ actions }: LilypadPricingProps) {
</Tabs>

{/* FAQ Section */}
<div className="bg-card border-border mt-16 rounded-lg border p-8">
<div className="border-border bg-card mt-16 rounded-lg border p-8">
<h2 className="text-foreground mb-4 text-2xl font-semibold">
Frequently Asked Questions
</h2>
Expand All @@ -338,8 +296,8 @@ export function LilypadPricing({ actions }: LilypadPricingProps) {
How long will the open beta last?
</h3>
<p className="text-muted-foreground">
The open beta period is ongoing, and we'll provide advance notice before moving to
paid plans.
The open beta period is ongoing, and we&apos;ll provide advance notice before moving
Comment thread
brenkao marked this conversation as resolved.
to paid plans.
</p>
</div>
<div>
Expand Down Expand Up @@ -374,3 +332,53 @@ export function LilypadPricing({ actions }: LilypadPricingProps) {
</div>
);
}

export const LilypadCloudPricing = ({
hostedActions,
}: {
hostedActions: {
free: TierAction;
pro: TierAction;
team: TierAction;
};
}) => {
const pricingTiers: PricingTierProps[] = [
{
name: "Free",
price: "$0",
description: "For individuals just getting started",
buttonText: hostedActions.free.buttonText,
buttonLink: hostedActions.free.buttonLink,
badge: "Open Beta",
variant: hostedActions.free.variant,
customButton: hostedActions.free.customButton,
},
{
name: "Pro",
price: "TBD",
description: "For teams with more advanced needs",
buttonText: hostedActions.pro.buttonText,
buttonLink: hostedActions.pro.buttonLink,
badge: "Closed Beta",
variant: hostedActions.pro.variant ?? "outline",
customButton: hostedActions.pro.customButton,
},
{
name: "Team",
price: "TBD",
description: "For larger teams requiring dedicated support",
buttonText: hostedActions.team.buttonText,
buttonLink: hostedActions.team.buttonLink,
badge: "Closed Beta",
variant: hostedActions.team.variant ?? "outline",
customButton: hostedActions.team.customButton,
},
];
return (
<div className="mb-10 grid gap-8 md:grid-cols-3">
{pricingTiers.map((tier) => (
<PricingTier key={tier.name} {...tier} />
))}
</div>
);
};