diff --git a/cloud/app/components/pricing-page.tsx b/cloud/app/components/pricing-page.tsx
new file mode 100644
index 0000000000..9d55e11bc6
--- /dev/null
+++ b/cloud/app/components/pricing-page.tsx
@@ -0,0 +1,425 @@
+import React from "react";
+import { cn } from "@/app/lib/utils";
+import { ButtonLink } from "@/app/components/ui/button-link";
+import {
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from "@/app/components/ui/tabs";
+import { Check, X } from "lucide-react";
+
+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 !== "";
+
+ return (
+
+
{feature}
+
+ {allSameNonBoolean ? (
+
+ {free}
+
+ ) : (
+ <>
+
+ {typeof free === "boolean" ? (
+
+ {free ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ ) : (
+
+ {free}
+
+ )}
+
+
+
+ {typeof pro === "boolean" ? (
+
+ {pro ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ ) : (
+
+ {pro}
+
+ )}
+
+
+
+ {typeof team === "boolean" ? (
+
+ {team ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+ ) : (
+
+ {team}
+
+ )}
+
+ >
+ )}
+
+ );
+};
+
+interface PricingTierProps {
+ name: string;
+ price: string;
+ description: string;
+ button: React.ReactNode;
+ badge?: "Open Beta" | "Closed Beta";
+}
+// Pricing tier component
+const PricingTier = ({
+ name,
+ price,
+ description,
+ button,
+ badge,
+}: PricingTierProps) => (
+
+
+
+
{name}
+ {badge && (
+
+ {badge}
+
+ )}
+
+
{description}
+
+ {price}
+ {price !== "TBD" && price !== "N/A" && (
+ / month
+ )}
+
+ {button}
+
+
+);
+
+// Feature comparison table component
+export const FeatureComparisonTable = ({
+ features,
+}: {
+ features: FeatureRowProps[];
+}) => (
+
+
+
+ Feature Comparison
+
+
+
+ {/* Table header */}
+
+
Feature
+
+ Free
+
+
+ Pro
+
+
+ Team
+
+
+
+ {/* Table rows */}
+ {features.map((feat, i) => (
+
+ ))}
+
+
+);
+
+interface TierAction {
+ button: React.ReactNode;
+}
+
+interface PricingActions {
+ hosted: {
+ free: TierAction;
+ pro: TierAction;
+ team: TierAction;
+ };
+ selfHosted: {
+ free: TierAction;
+ pro: TierAction;
+ team: TierAction;
+ };
+}
+
+// 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 PricingProps {
+ actions: PricingActions;
+}
+
+export function PricingPage({ actions }: PricingProps) {
+ return (
+
+
+
+
+ Pricing
+
+
+ Get started with the Free plan today.
+
+
+ No credit card required.
+
+
+
+
+
+
+ Hosted By Us
+ Self Hosting
+
+
+
+ {/* Hosted By Us Tab Content */}
+
+
+
+ {/* Feature comparison table */}
+
+
+
+ {/* Self Hosting Tab Content */}
+
+
+
+ {/* Feature comparison table */}
+
+
+
+
+ {/* FAQ Section */}
+
+
+ Frequently Asked Questions
+
+
+
+
+ How long will the open beta last?
+
+
+ The open beta period is ongoing, and we'll provide advance
+ notice before moving to paid plans.
+
+
+
+
+ What happens when the beta ends?
+
+
+ All existing users will receive a grace period to evaluate which
+ plan is right for them before making any changes.
+
+
+
+
+
+
+
+ Have questions about our pricing?
+
+
+ Join our{" "}
+
+ community
+ {" "}
+ and ask the team directly!
+
+
+
+
+ );
+}
+
+export const CloudPricing = ({
+ hostedActions,
+}: {
+ hostedActions: {
+ free: TierAction;
+ pro: TierAction;
+ team: TierAction;
+ };
+}) => {
+ const pricingTiers: PricingTierProps[] = [
+ {
+ name: "Free",
+ price: "$0",
+ description: "For individuals just getting started",
+ badge: "Open Beta",
+ button: hostedActions.free.button,
+ },
+ {
+ name: "Pro",
+ price: "TBD",
+ description: "For teams with more advanced needs",
+ badge: "Closed Beta",
+ button: hostedActions.pro.button,
+ },
+ {
+ name: "Team",
+ price: "TBD",
+ description: "For larger teams requiring dedicated support",
+ badge: "Closed Beta",
+ button: hostedActions.team.button,
+ },
+ ];
+ return (
+
+ {pricingTiers.map((tier) => (
+
+ ))}
+
+ );
+};
diff --git a/cloud/app/routes/pricing.tsx b/cloud/app/routes/pricing.tsx
index 2ce6ae8029..025adafe62 100644
--- a/cloud/app/routes/pricing.tsx
+++ b/cloud/app/routes/pricing.tsx
@@ -1,7 +1,66 @@
import { createFileRoute } from "@tanstack/react-router";
-import { TempPage } from "@/app/components/temp-page";
+import { PricingPage } from "@/app/components/pricing-page";
+import { ButtonLink } from "@/app/components/ui/button-link";
+
+const marketingActions = {
+ hosted: {
+ free: {
+ button: (
+
+ Get Started
+
+ ),
+ },
+ pro: {
+ button: (
+
+ Contact Us
+
+ ),
+ },
+ team: {
+ button: (
+
+ Contact Us
+
+ ),
+ },
+ },
+ selfHosted: {
+ free: {
+ button: (
+
+ Get Started
+
+ ),
+ },
+ pro: {
+ button: (
+
+ Request License
+
+ ),
+ },
+ team: {
+ button: (
+
+ Request License
+
+ ),
+ },
+ },
+};
export const Route = createFileRoute("/pricing")({
- // todo(sebastian): temp route ahead of porting md/mdx files
- component: () => ,
+ // todo(sebastian): simplify and add other SEO metadata
+ head: () => ({
+ meta: [
+ { title: "Pricing" },
+ {
+ name: "description",
+ content: "Mirascope cloud's pricing plans and features",
+ },
+ ],
+ }),
+ component: () => ,
});