From 912a0ffef680de68f49a79144e91c617b1ad24c5 Mon Sep 17 00:00:00 2001 From: Brendan Date: Wed, 4 Jun 2025 15:35:42 -0700 Subject: [PATCH 1/3] feat: update lilypad pricing to be multiple components --- mirascope-ui/blocks/lilypad-pricing.tsx | 240 ++++++++++++------------ 1 file changed, 124 insertions(+), 116 deletions(-) diff --git a/mirascope-ui/blocks/lilypad-pricing.tsx b/mirascope-ui/blocks/lilypad-pricing.tsx index 4fcef72..4cbbbe6 100644 --- a/mirascope-ui/blocks/lilypad-pricing.tsx +++ b/mirascope-ui/blocks/lilypad-pricing.tsx @@ -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 !== ""; @@ -85,6 +81,16 @@ 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"; +} // Pricing tier component const PricingTier = ({ name, @@ -92,18 +98,11 @@ const PricingTier = ({ 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"; -}) => ( -
+}: PricingTierProps) => ( +

{name}

@@ -127,26 +126,19 @@ const PricingTier = ({ / month )}
- - {buttonText} - + {customButton ?? ( + + {buttonText} + + )}
); // Feature comparison table component -const FeatureComparisonTable = ({ - features, -}: { - features: Array<{ - feature: string; - free: string | boolean; - pro: string | boolean; - team: string | boolean; - }>; -}) => ( -
-
+export const FeatureComparisonTable = ({ features }: { features: FeatureRowProps[] }) => ( +
+

Feature Comparison

@@ -160,22 +152,17 @@ const FeatureComparisonTable = ({ {/* Table rows */} {features.map((feat, i) => ( - + ))}
); interface TierAction { - buttonText: string; - buttonLink: string; + buttonText?: string; + buttonLink?: string; variant?: "default" | "outline"; + customButton?: React.ReactNode; } interface PricingActions { @@ -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 (
@@ -256,35 +242,7 @@ export function LilypadPricing({ actions }: LilypadPricingProps) { {/* Hosted By Us Tab Content */} -
- - - -
+ {/* Feature comparison table */} @@ -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"} />
@@ -328,7 +286,7 @@ export function LilypadPricing({ actions }: LilypadPricingProps) { {/* FAQ Section */} -
+

Frequently Asked Questions

@@ -338,8 +296,8 @@ export function LilypadPricing({ actions }: LilypadPricingProps) { How long will the open beta last?

- 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'll provide advance notice before moving + to paid plans.

@@ -374,3 +332,53 @@ export function LilypadPricing({ actions }: LilypadPricingProps) {
); } + +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 ( +
+ {pricingTiers.map((tier) => ( + + ))} +
+ ); +}; From 394eef762471953b78c1550d2cda7c56d03ba321 Mon Sep 17 00:00:00 2001 From: Brendan Date: Wed, 4 Jun 2025 16:43:59 -0700 Subject: [PATCH 2/3] feat: update slack link --- mirascope-ui/blocks/lilypad-pricing.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mirascope-ui/blocks/lilypad-pricing.tsx b/mirascope-ui/blocks/lilypad-pricing.tsx index 4cbbbe6..3cb2947 100644 --- a/mirascope-ui/blocks/lilypad-pricing.tsx +++ b/mirascope-ui/blocks/lilypad-pricing.tsx @@ -319,7 +319,7 @@ export function LilypadPricing({ actions }: LilypadPricingProps) {

Join our{" "} From 840d9b549119c5b54f3e7cfd1b2bd9e4f320695d Mon Sep 17 00:00:00 2001 From: Brendan Date: Thu, 5 Jun 2025 08:03:30 -0700 Subject: [PATCH 3/3] remove button link --- .../blocks/lilypad-pricing.stories.tsx | 145 +++++++++++------- mirascope-ui/blocks/lilypad-pricing.tsx | 54 ++----- 2 files changed, 101 insertions(+), 98 deletions(-) diff --git a/mirascope-ui/blocks/lilypad-pricing.stories.tsx b/mirascope-ui/blocks/lilypad-pricing.stories.tsx index bd3ce48..a7a80be 100644 --- a/mirascope-ui/blocks/lilypad-pricing.stories.tsx +++ b/mirascope-ui/blocks/lilypad-pricing.stories.tsx @@ -1,4 +1,5 @@ import { LilypadPricing } from "@/mirascope-ui/blocks/lilypad-pricing"; +import { ButtonLink } from "@/mirascope-ui/ui/button-link"; import type { Meta, StoryObj } from "@storybook/react"; import { createMemoryHistory, @@ -55,36 +56,48 @@ type Story = StoryObj; const defaultActions = { hosted: { free: { - buttonText: "Get Started Free", - buttonLink: "/signup", - variant: "default" as const, + button: ( + + Get Started Free + + ), }, pro: { - buttonText: "Join Waitlist", - buttonLink: "/waitlist", - variant: "outline" as const, + button: ( + + Join Waitlist + + ), }, team: { - buttonText: "Contact Sales", - buttonLink: "/contact", - variant: "outline" as const, + button: ( + + Contact Sales + + ), }, }, selfHosted: { free: { - buttonText: "Download", - buttonLink: "/download", - variant: "default" as const, + button: ( + + Download + + ), }, pro: { - buttonText: "Request Access", - buttonLink: "/request-access", - variant: "outline" as const, + button: ( + + Request Access + + ), }, team: { - buttonText: "Contact Sales", - buttonLink: "/contact", - variant: "outline" as const, + button: ( + + Contact Sales + + ), }, }, }; @@ -100,36 +113,48 @@ export const WithExternalLinks: Story = { actions: { hosted: { free: { - buttonText: "Sign Up Now", - buttonLink: "https://app.lilypad.com/signup", - variant: "default" as const, + button: ( + + Sign Up Now + + ), }, pro: { - buttonText: "Join Beta", - buttonLink: "https://forms.lilypad.com/beta", - variant: "outline" as const, + button: ( + + Join Beta + + ), }, team: { - buttonText: "Schedule Demo", - buttonLink: "https://calendly.com/lilypad-team", - variant: "outline" as const, + button: ( + + Schedule Demo + + ), }, }, selfHosted: { free: { - buttonText: "GitHub Release", - buttonLink: "https://github.com/mirascope/lilypad/releases", - variant: "default" as const, + button: ( + + GitHub Release + + ), }, pro: { - buttonText: "Enterprise Inquiry", - buttonLink: "https://forms.lilypad.com/enterprise", - variant: "outline" as const, + button: ( + + Enterprise Inquiry + + ), }, team: { - buttonText: "Book Consultation", - buttonLink: "https://calendly.com/lilypad-enterprise", - variant: "outline" as const, + button: ( + + Book Consultation + + ), }, }, }, @@ -148,36 +173,48 @@ export const AllPrimaryButtons: Story = { actions: { hosted: { free: { - buttonText: "Start Free", - buttonLink: "/start", - variant: "default" as const, + button: ( + + Start Free + + ), }, pro: { - buttonText: "Upgrade to Pro", - buttonLink: "/upgrade-pro", - variant: "default" as const, + button: ( + + Upgrade to Pro + + ), }, team: { - buttonText: "Get Team Plan", - buttonLink: "/upgrade-team", - variant: "default" as const, + button: ( + + Get Team Plan + + ), }, }, selfHosted: { free: { - buttonText: "Download Free", - buttonLink: "/download", - variant: "default" as const, + button: ( + + Download Free + + ), }, pro: { - buttonText: "Get Pro License", - buttonLink: "/license-pro", - variant: "default" as const, + button: ( + + Get Pro License + + ), }, team: { - buttonText: "Get Team License", - buttonLink: "/license-team", - variant: "default" as const, + button: ( + + Get Team License + + ), }, }, }, diff --git a/mirascope-ui/blocks/lilypad-pricing.tsx b/mirascope-ui/blocks/lilypad-pricing.tsx index 3cb2947..21dc8f8 100644 --- a/mirascope-ui/blocks/lilypad-pricing.tsx +++ b/mirascope-ui/blocks/lilypad-pricing.tsx @@ -85,23 +85,11 @@ interface PricingTierProps { name: string; price: string; description: string; - buttonText?: string; - buttonLink?: string; - customButton?: React.ReactNode; + button: React.ReactNode; badge?: "Open Beta" | "Closed Beta"; - variant?: "default" | "outline"; } // Pricing tier component -const PricingTier = ({ - name, - price, - description, - buttonText, - buttonLink, - customButton, - badge, - variant = "default", -}: PricingTierProps) => ( +const PricingTier = ({ name, price, description, button, badge }: PricingTierProps) => (

@@ -126,11 +114,7 @@ const PricingTier = ({ / month )}
- {customButton ?? ( - - {buttonText} - - )} + {button}
); @@ -159,10 +143,7 @@ export const FeatureComparisonTable = ({ features }: { features: FeatureRowProps ); interface TierAction { - buttonText?: string; - buttonLink?: string; - variant?: "default" | "outline"; - customButton?: React.ReactNode; + button: React.ReactNode; } interface PricingActions { @@ -255,28 +236,22 @@ export function LilypadPricing({ actions }: LilypadPricingProps) { name="Free" price="$0" description="For individuals just getting started" - buttonText={actions.selfHosted.free.buttonText} - buttonLink={actions.selfHosted.free.buttonLink} badge="Open Beta" - variant={actions.selfHosted.free.variant} + button={actions.selfHosted.free.button} />
@@ -347,31 +322,22 @@ export const LilypadCloudPricing = ({ 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, + button: hostedActions.free.button, }, { 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, + button: hostedActions.pro.button, }, { 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, + button: hostedActions.team.button, }, ]; return (