diff --git a/apps/site/public/og/og-pricing.png b/apps/site/public/og/og-pricing.png new file mode 100644 index 0000000000..3fe6d5579a Binary files /dev/null and b/apps/site/public/og/og-pricing.png differ diff --git a/apps/site/src/app/pricing/page.tsx b/apps/site/src/app/pricing/page.tsx new file mode 100644 index 0000000000..2b010d5bdc --- /dev/null +++ b/apps/site/src/app/pricing/page.tsx @@ -0,0 +1,187 @@ +import type { Metadata } from "next"; +import { + Accordion, + Accordions, + Badge, + Button, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@prisma/eclipse"; +import { comparisonSections, faqs } from "./pricing-data"; +import { PricingPageContent } from "./pricing-page-content"; + +export const metadata: Metadata = { + title: "Pricing | Prisma Postgres", + description: "Get started for free using Prisma's products or choose the right plan that meets your needs", + alternates: { + canonical: "https://www.prisma.io/pricing", + }, + openGraph: { + title: "Pricing | Prisma Postgres", + description: + "Get started for free using Prisma's products or choose the right plan that meets your needs", + url: "https://www.prisma.io/pricing", + images: [ + { + url: "/og/og-pricing.png", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: "Pricing | Prisma Postgres", + description: + "Get started for free using Prisma's products or choose the right plan that meets your needs", + images: ["/og/og-pricing.png"], + }, +}; + +export default function PricingPage() { + return ( +
+ + + {/* Compare plans */} +
+
+

+ Compare plans +

+

+ All of the features below are included with Prisma Postgres. +

+
+
+ + + + + {comparisonSections[0]?.title} + + {["Free", "Starter", "Pro", "Business"].map((label) => ( + + + + ))} + + + {comparisonSections.map((section) => ( + + + + {section.title} + + + + {section.rows.map((row) => ( + + + {row[0]} + + {row.slice(1).map((value, valueIndex) => ( + + {value} + + ))} + + ))} + + ))} +
+
+
+ + {/* FAQ */} +
+
+

+ FAQ +

+ + {faqs.map((faq, index) => ( + +
+ + ))} + +

+ If you have any questions, please reach out to our support team at{" "} + + support@prisma.io + + . +

+
+
+ + {/* Try Prisma Postgres */} +
+
+
+ Try Prisma Postgres +
+

+ Deploy a Postgres database instantly. +

+
+ + +
+

+ Free to get started, no credit card needed. +

+
+
+
+ ); +} diff --git a/apps/site/src/app/pricing/pricing-calculator.tsx b/apps/site/src/app/pricing/pricing-calculator.tsx new file mode 100644 index 0000000000..be232474f3 --- /dev/null +++ b/apps/site/src/app/pricing/pricing-calculator.tsx @@ -0,0 +1,634 @@ +"use client"; +import * as React from "react"; +import { + Alert, + Badge, + Button, + Slider, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@prisma/eclipse"; +import { + plans, + type BillablePricingPlanKey, + type Symbol, + symbols, + usagePricing, +} from "./pricing-data"; + +function cn(...classes: Array) { + return classes.filter(Boolean).join(" "); +} + +type PresetKey = "hobby" | "startup" | "scaleup"; +type BillingCycle = "monthly" | "yearly"; +type RecommendedSelection = BillablePricingPlanKey | "enterprise"; +type CostBreakdown = { + basePlanFee: number; + billableOperations: number; + operationsCost: number; + billableStorageGb: number; + storageCost: number; +}; + +const SQL_QUERY_MULTIPLIER = 5; +const ENTERPRISE_OPERATIONS_THRESHOLD = 280_000_000; +const MAX_DATABASE_OPERATIONS = 300_000_000; + +const PRESETS: Record< + PresetKey, + { + label: string; + icon: string; + databaseOperations: number; + storageGb: number; + } +> = { + hobby: { + label: "Hobby", + icon: "fa-regular fa-rocket-launch", + databaseOperations: 12_000_000, + storageGb: 8, + }, + startup: { + label: "Startup", + icon: "fa-solid fa-bolt", + databaseOperations: 36_000_000, + storageGb: 18, + }, + scaleup: { + label: "Scaleup", + icon: "fa-solid fa-building", + databaseOperations: 84_000_000, + storageGb: 40, + }, +}; + +const CALCULATOR_PLAN_ORDER = Object.keys( + usagePricing, +) as BillablePricingPlanKey[]; + +function formatNumber(value: number) { + return new Intl.NumberFormat("en-US").format(Math.round(value)); +} + +function formatCurrency(value: number, currency: Symbol, digits = 2) { + return `${symbols[currency]}${value.toLocaleString("en-US", { + minimumFractionDigits: digits, + maximumFractionDigits: digits, + })}`; +} + +function formatCompactCurrency(value: number, currency: Symbol) { + return `${symbols[currency]}${value.toLocaleString("en-US", { + minimumFractionDigits: Number.isInteger(value) ? 0 : 1, + maximumFractionDigits: 2, + })}`; +} + +function formatLineItemCost(value: number, currency: Symbol) { + if (value <= 0) { + return "Free"; + } + + return formatCurrency(value, currency); +} + +function getPlanDescription(plan: BillablePricingPlanKey, currency: Symbol) { + const details = usagePricing[plan]; + + return `${formatNumber(details.includedOperations)} ops included, then ${formatCompactCurrency(details.operationPricePerThousand, currency)} per 1,000 • ${details.includedStorageGb}GB included, then ${formatCompactCurrency(details.storagePricePerGb, currency)}/GB`; +} + +function calculateMonthlyPlanCost( + plan: BillablePricingPlanKey, + databaseOperations: number, + storageGb: number, +) { + const details = usagePricing[plan]; + const extraOperations = Math.max(0, databaseOperations - details.includedOperations); + const extraStorageGb = Math.max(0, storageGb - details.includedStorageGb); + + return ( + details.baseMonthlyPrice + + extraOperations / 1_000 * details.operationPricePerThousand + + extraStorageGb * details.storagePricePerGb + ); +} + +function calculateDisplayedPlanCost( + plan: BillablePricingPlanKey, + databaseOperations: number, + storageGb: number, + billingCycle: BillingCycle, +) { + const monthlyCost = calculateMonthlyPlanCost(plan, databaseOperations, storageGb); + + if (billingCycle === "monthly") { + return monthlyCost; + } + + return monthlyCost * (1 - usagePricing[plan].yearlyDiscount); +} + +function calculatePlanBreakdown( + plan: BillablePricingPlanKey, + databaseOperations: number, + storageGb: number, + billingCycle: BillingCycle, +): CostBreakdown { + const details = usagePricing[plan]; + const billableOperations = Math.max( + 0, + databaseOperations - details.includedOperations, + ); + const billableStorageGb = Math.max(0, storageGb - details.includedStorageGb); + const operationsCost = + billableOperations / 1_000 * details.operationPricePerThousand; + const storageCost = billableStorageGb * details.storagePricePerGb; + const yearlyMultiplier = + billingCycle === "yearly" ? 1 - details.yearlyDiscount : 1; + + return { + basePlanFee: details.baseMonthlyPrice * yearlyMultiplier, + billableOperations, + operationsCost: operationsCost * yearlyMultiplier, + billableStorageGb, + storageCost: storageCost * yearlyMultiplier, + }; +} + +function getRecommendedPlan( + databaseOperations: number, + storageGb: number, + billingCycle: BillingCycle, +): RecommendedSelection { + if (databaseOperations >= ENTERPRISE_OPERATIONS_THRESHOLD) { + return "enterprise"; + } + + return CALCULATOR_PLAN_ORDER.reduce((bestPlan, candidatePlan) => { + const bestCost = calculateDisplayedPlanCost( + bestPlan, + databaseOperations, + storageGb, + billingCycle, + ); + const candidateCost = calculateDisplayedPlanCost( + candidatePlan, + databaseOperations, + storageGb, + billingCycle, + ); + + return candidateCost < bestCost ? candidatePlan : bestPlan; + }); +} + +function getMatchingPreset( + databaseOperations: number, + storageGb: number, +): PresetKey | null { + const match = ( + Object.entries(PRESETS) as Array<[PresetKey, (typeof PRESETS)[PresetKey]]> + ).find( + ([, preset]) => + preset.databaseOperations === databaseOperations && + preset.storageGb === storageGb, + ); + + return match?.[0] ?? null; +} + +function InputShell({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) { + return ( +
+ {children} +
+ ); +} + +function SummaryCard({ + title, + description, + price, + currency, + breakdown, + plan, + highlighted = false, + expanded = false, + onToggle, + yearly = false, +}: { + title: string; + description: string; + price: number; + currency: Symbol; + breakdown: CostBreakdown; + plan: BillablePricingPlanKey; + highlighted?: boolean; + expanded?: boolean; + onToggle: () => void; + yearly?: boolean; +}) { + const planDetails = usagePricing[plan]; + + return ( +
+
+
+
+

+ {title} +

+ {highlighted && ( + + )} +
+

+

+ + +
+ + {expanded && ( +
+
+
+ Base plan fee + + {formatLineItemCost(breakdown.basePlanFee, currency)} + +
+ +
+
+ Billable database operations + + + + + + First {formatNumber(planDetails.includedOperations)} operations + are included in this plan. Remaining{" "} + {formatNumber(breakdown.billableOperations)} operations are + billed at{" "} + {formatCompactCurrency( + planDetails.operationPricePerThousand, + currency, + )}{" "} + per 1,000. + + +
+ + {formatLineItemCost(breakdown.operationsCost, currency)} + +
+ +
+
+ Billable storage + + + + + + First {formatNumber(planDetails.includedStorageGb)}GB of + storage are included. Remaining{" "} + {formatNumber(breakdown.billableStorageGb)}GB are billed at{" "} + {formatCompactCurrency(planDetails.storagePricePerGb, currency)} + /GB. + + +
+ + {formatLineItemCost(breakdown.storageCost, currency)} + +
+
+
+ )} +
+ ); +} + +export function PricingCalculator({ currency }: { currency: Symbol }) { + const [lastAppliedPreset, setLastAppliedPreset] = + React.useState("scaleup"); + const [billingCycle, setBillingCycle] = + React.useState("monthly"); + const [expandedPlan, setExpandedPlan] = + React.useState(null); + const [databaseOperations, setDatabaseOperations] = React.useState( + PRESETS.scaleup.databaseOperations, + ); + const [storageGb, setStorageGb] = React.useState(PRESETS.scaleup.storageGb); + + const applyPreset = React.useCallback((nextPreset: PresetKey) => { + const values = PRESETS[nextPreset]; + setLastAppliedPreset(nextPreset); + setDatabaseOperations(values.databaseOperations); + setStorageGb(values.storageGb); + }, []); + + const reset = React.useCallback(() => { + applyPreset(lastAppliedPreset); + }, [applyPreset, lastAppliedPreset]); + + const estimatedSqlQueries = databaseOperations * SQL_QUERY_MULTIPLIER; + const matchingPreset = React.useMemo( + () => getMatchingPreset(databaseOperations, storageGb), + [databaseOperations, storageGb], + ); + const recommendedPlanForUsage = React.useMemo( + () => getRecommendedPlan(databaseOperations, storageGb, billingCycle), + [databaseOperations, storageGb, billingCycle], + ); + const isEnterpriseRecommendation = recommendedPlanForUsage === "enterprise"; + + return ( + +
+
+
+

+ Pricing Calculator +

+ +
+
+ Quick Start Presets +
+
+ {(Object.entries(PRESETS) as Array<[PresetKey, (typeof PRESETS)[PresetKey]]>).map( + ([key, item]) => { + const active = key === matchingPreset; + + return ( + + ); + }, + )} +
+
+
+
+ +
+
+
+ +

+ Estimate your monthly usage +

+ +
+ +
+
+
+ + Database Operations + +
+ {formatNumber(databaseOperations)} + setDatabaseOperations(value[0] ?? databaseOperations)} + /> +
+ +
+
+ Estimated SQL Queries + + {SQL_QUERY_MULTIPLIER}x + + +
+ {formatNumber(estimatedSqlQueries)} +
+ +
+
+ + Storage +
+ + {formatNumber(storageGb)} + + GB + + + setStorageGb(value[0] ?? storageGb)} + /> +
+ +
+ {/*
+
Compute Size
+
+ Included and auto-scaled by Prisma Postgres +
+

+ vCPU, RAM, cores, micro, xl, C-3PO... etc. +

+
*/} + +
+
Data Transfer
+
+ Unlimited included for free +
+

+ Ingress, egress, sidewaysgress, it's all covered. Just Ship It. +

+
+
+
+
+ +
+
+
+ +

+ Estimated total cost +

+
+ +
+ {(["monthly", "yearly"] as BillingCycle[]).map((cycle) => { + const active = cycle === billingCycle; + + return ( + + ); + })} +
+
+ +
+ {isEnterpriseRecommendation && ( + +

+ Usage at this scale is best served on an enterprise plan. Reach + out to{" "} + + support@prisma.io + {" "} + for pricing. +

+
+ )} + {CALCULATOR_PLAN_ORDER.map((plan) => ( + + setExpandedPlan((current) => (current === plan ? null : plan)) + } + yearly={billingCycle === "yearly"} + price={calculateDisplayedPlanCost( + plan, + databaseOperations, + storageGb, + billingCycle, + )} + /> + ))} +
+
+
+
+
+ ); +} diff --git a/apps/site/src/app/pricing/pricing-data.ts b/apps/site/src/app/pricing/pricing-data.ts new file mode 100644 index 0000000000..fa87098395 --- /dev/null +++ b/apps/site/src/app/pricing/pricing-data.ts @@ -0,0 +1,271 @@ +export const symbols = { + EUR: "€", + AUD: "A$", + INR: "₹", + GBP: "£", + CAD: "C$", + BRL: "R$", + JPY: "¥", + CNY: "C¥", + KRW: "₩", + USD: "$", +} as const; + +export type Symbol = + | "EUR" + | "AUD" + | "INR" + | "GBP" + | "CAD" + | "BRL" + | "JPY" + | "CNY" + | "KRW" + | "USD"; + +export type CurrencyMap = Record; + +export type PlanPoint = + | string + | { + text: string; + price: CurrencyMap; + } + +export type PricingPlan = { + title: string; + subtitle: string; + points: PlanPoint[]; + price: CurrencyMap; +}; + +export type UsagePricing = { + baseMonthlyPrice: number; + includedOperations: number; + includedStorageGb: number; + operationPricePerThousand: number; + storagePricePerGb: number; + yearlyDiscount: number; +}; + +/** Formats a single numeric amount with every supported currency symbol. */ +export function formatAmountForAllCurrencies( + amount: number, + digits: number, +): CurrencyMap { + return Object.fromEntries( + Object.entries(symbols).map(([code, symbol]) => [ + code, + `${symbol}${amount.toFixed(digits)}`, + ]), + ) as CurrencyMap; +} + +/** Replaces the inline price token in a plan bullet for the active currency. */ +export function renderPlanPoint(point: PlanPoint, currency: Symbol): string { + if (typeof point === "string") { + return point; + } + + return point.text.replace("", point.price[currency]); +} + +export const planOrder = ["free", "starter", "pro", "business"] as const; +export type PricingPlanKey = (typeof planOrder)[number]; +export type BillablePricingPlanKey = Exclude; + +export const planActions: Record = { + free: "Start for Free", + starter: "Get started", + pro: "Start building", + business: "Start building", +}; + +export const plans: Record = { + free: { + title: "Free", + subtitle: "Perfect for that weekend idea", + points: [ + "100,000 operations* included", + "500 MB storage", + "5 databases", + "No credit card required", + ], + price: formatAmountForAllCurrencies(0, 0), + }, + starter: { + title: "Starter", + subtitle: "The basics you need to launch", + points: [ + { + text: "1,000,000 operations*  included, then per 1,000 ", + price: formatAmountForAllCurrencies(0.008, 4), + }, + { + text: "10 GB storage included
then per GB", + price: formatAmountForAllCurrencies(2, 2), + }, + "10 databases", + "Includes spend limits
Daily backups stored for 7 days", + ], + price: formatAmountForAllCurrencies(10, 0), + }, + pro: { + title: "Pro", + subtitle: "Growing for business success", + points: [ + { + text: "10,000,000 operations* included, then per 1,000", + price: formatAmountForAllCurrencies(0.002, 4), + }, + { + text: "50 GB storage included
then per GB", + price: formatAmountForAllCurrencies(1.5, 2), + }, + "100 databases", + "Includes spend limits
Daily backups stored for 7 days", + ], + price: formatAmountForAllCurrencies(49, 0), + }, + business: { + title: "Business", + subtitle: "For mission-critical apps", + points: [ + { + text: "50,000,000 operations* included, then per 1,000", + price: formatAmountForAllCurrencies(0.001, 4), + }, + { + text: "100 GB storage included
then per GB", + price: formatAmountForAllCurrencies(1, 2), + }, + "1000 databases", + "Includes spend limits
Daily backups stored for 30 days", + ], + price: formatAmountForAllCurrencies(129, 0), + }, +}; + +export const usagePricing: Record = { + starter: { + baseMonthlyPrice: 10, + includedOperations: 1_000_000, + includedStorageGb: 10, + operationPricePerThousand: 0.008, + storagePricePerGb: 2, + yearlyDiscount: 0.25, + }, + pro: { + baseMonthlyPrice: 49, + includedOperations: 10_000_000, + includedStorageGb: 50, + operationPricePerThousand: 0.002, + storagePricePerGb: 1.5, + yearlyDiscount: 0.25, + }, + business: { + baseMonthlyPrice: 129, + includedOperations: 50_000_000, + includedStorageGb: 100, + operationPricePerThousand: 0.001, + storagePricePerGb: 1, + yearlyDiscount: 0.25, + }, +}; + +export const comparisonSections = [ + { + title: "Managed Connection Pool", + rows: [ + ["Connection limit (direct)", "10", "10", "50", "100"], + ["Connection limit (pooled)", "10", "100", "500", "1000"], + ["Connection idle timeout", "60 minutes", "60 minutes", "60 minutes", "60 minutes"], + ["Auto-scaling", "✓", "✓", "✓", "✓"], + ["Operation response size", "unlimited", "unlimited", "unlimited", "unlimited"], + ["Operation duration for db queries", "unlimited", "unlimited", "unlimited", "unlimited"], + ["Operation duration for interactive", "unlimited", "unlimited", "unlimited", "unlimited"], + ], + }, + { + title: "Global Cache", + rows: [ + ["Cache tag invalidations", "-", "-", "$0.002 per 1,000, max 10,000 per day", "$0.001 per 1,000, max 100,000 per day"], + ["Cache purge requests", "5 per hour", "5 per hour", "10 per hour", "20 per hour"], + ], + }, + { + title: "Database optimizations", + rows: [ + ["Query insights", "✓", "✓", "✓", "✓"], + ], + }, + { + title: "Data management", + rows: [["View and edit your data", "✓", "✓", "✓", "✓"]], + }, + { + title: "Platform", + rows: [ + ["Support", "Community", "Community", "Standard", "Premium"], + ["Compliance", "GDPR", "GDPR", "GDPR / HIPAA", "GDPR / HIPAA / SOC2 / ISO:27001"], + ], + }, +] as const; + +export const faqs: Array<{ question: string; answer: string }> = [ + { + question: "What is an operation?", + answer: + "

Each action you perform, whether it’s a create, read, update, or delete query against your Prisma Postgres database counts as a single operation. Even if Prisma issues multiple database queries behind the scenes to fulfill your request, it’s still billed as one operation.

By treating simple lookups and complex queries the same, you can directly correlate your database usage and costs with your product usage and user behavior. There’s no need to track write-heavy workloads or worry about bandwidth per operation: each of them is counted and billed the same, making your usage and budgeting simple and straightforward. You can learn more about our operations-based pricing model in our blog post.

", + }, + { + question: "How many operations do I need for my project?", + answer: + "

While the answer to this question will vary from project to project, there are a couple of ways to get an idea of what you will need:

  • If you already have a database with another provider, you can often look in their dashboard to see your current usage. The number of queries will be a good hint to the approximate number of operations you’ll use.
  • If you already use the Prisma ORM, you can enable the metrics feature to begin tracking your usage, which is an easy and accurate way to see your current usage.
  • If you’re starting a new project, we encourage you to just get started and see how many queries you typically use. We offer a free plan with 100,000 operations per month, meaning you can confidently get started without paying anything. From our experience, 100,000 operations per month is more than enough to get started with a project and serve your first users.

You can find an example calculation for a medium-sized workload in our blog post about our operations-based pricing model.

", + }, + { + question: "Can I use Prisma Postgres for free?", + answer: + "

We include a free threshold of 100,000 database operations per month on the Free plan, meaning you can use Prisma for free, and only pay if you exceed the threshold. From our experience, 100,000 operations per month is more than enough to get started.

We always send usage notifications to let you know when you’re approaching the threshold, so that you’re always in control of your spending.

", + }, + { + question: "Can I set spending limits to control my budget?", + answer: + "

Yes, you can set limits to ensure you never get a surprise bill. We’ll send you alerts when you reach 75% of your set limit, and if you reach 100% we’ll pause access to your database. This ensures you’ll never have an unexpected bill, and you can always be in complete control of your spending.

", + }, + { + question: "Why do you count usage on account level, rather than database level?", + answer: + "

We record usage at the account level because it gives you, the developer, the most flexibility. You can spin up one database or 20 databases without any extra cost — pay only for the operations you make and storage you use across all of them.

This makes experimenting, prototyping and testing ideas super easy and seamless, because you don't have to think about how many databases you create.

", + }, + { + question: "What’s the difference between usage pricing and traditional database pricing?", + answer: + "

Traditional pricing is where you choose a fixed database size and price, and the amount you pay is generally predictable. But that comes at the expense of flexibility, meaning it’s much harder to scale up and down with your application’s demands. This is usually fine for a small test database, but for production workloads, it can be burdensome: If you have low-traffic periods, and high-traffic periods (most production apps do) then you either under-provision and risk having downtime in busy periods, or you over-provision and pay a lot more for your database.

With usage pricing, you only pay for what you need, when you need it. If your app has a quiet period, you’ll pay less. If things get busy, we can simply scale up to handle it for you. Prisma Postgres comes with budget controls, so you can always stay in control of your spending, while taking advantage of the flexibility. You can learn more on why operations-based pricing is better in our blog post.

", + }, + { + question: "How is Prisma’s pricing different to others?", + answer: + "

Prisma’s pricing is designed to provide maximum flexibility to developers, while aiming to be as intuitive as possible.

We charge primarily by operation, which is counted each time you invoke the Prisma ORM client to create, read, update or delete a record. Additionally we also charge for storage. All with a very generous free threshold each month.

We don’t charge by data transfer (bandwidth) or by compute/memory hours, simply because we felt that these metrics are more difficult to grasp as a developer.

We created a pricing model to more closely match how you use your database as a developer, not how the infrastructure works. You can learn more about our approach to an operations-based database pricing model in this blog post.

", + }, + { + question: "How can I compare Prisma pricing to other providers?", + answer: + "

Because we only charge you for what you actually use, the best way to see a comparison is to run your application on Prisma, and that’s why we offer a free threshold every month.

However, as a simple comparison, the average database operation size from current Prisma users is 10kb (measured from over 15b queries). Some providers charge by bandwidth used, meaning 5GB of bandwidth might equate to approximately 500,000 database operations.

", + }, + { + question: "Can I get the power of Prisma with my own database?", + answer: + "

You can also connect your own database to Prisma's global caching and connection pooling, also known as Prisma Accelerate.

Click the "Bring your own database" toggle at the top of this page to see more
detail.

", + }, + { + question: "I'm an early stage startup, do you offer any discounts?", + answer: + "

Building a startup is hard. Prisma helps you stay laser-focused on what matters the most, which is building features and winning users.

We offer $10k in credits to eligible startups. Learn more at prisma.io/startups.

", + }, + { + question: "How do I upgrade my plan if I am using Prisma Postgres via Vercel?", + answer: + "

If you're using Prisma Postgres via Vercel, your billing is handled directly by Vercel. To upgrade your plan, you'll need to do so in the Vercel Dashboard. The instructions are available in our docs.

", + }, +]; diff --git a/apps/site/src/app/pricing/pricing-hero-plans.tsx b/apps/site/src/app/pricing/pricing-hero-plans.tsx new file mode 100644 index 0000000000..a882dfb451 --- /dev/null +++ b/apps/site/src/app/pricing/pricing-hero-plans.tsx @@ -0,0 +1,190 @@ +"use client"; + +import Image from "next/image"; +import { + Badge, + Button, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@prisma/eclipse"; +import { + type Symbol, + type PlanPoint, + planActions, + planOrder, + plans, + renderPlanPoint, + symbols, +} from "./pricing-data"; + +export function PricingHeroPlans({ + currency, + onCurrencyChange, +}: { + currency: Symbol; + onCurrencyChange: (currency: Symbol) => void; +}) { + const getPlanPointKey = (planKey: string, item: PlanPoint, index: number) => { + if (typeof item === "string") { + return `${planKey}-${item}`; + } + + return `${planKey}-${item.text}-${index}`; + }; + + return ( + <> +
+
+
+ + + Prisma ORM will always be free + + } + /> +

+ Scale as You Grow
with Prisma Postgres +

+

+ Operation-based pricing. We only charge for what you use. +

+
+
+ +
+
+
+ +
+
+ {planOrder.map((planKey) => { + const plan = plans[planKey]; + const highlighted = planKey === "pro"; + + return ( +
+ {highlighted && ( + + )} +
+

+ {plan.title} +

+ {(planKey === "pro" || planKey === "business") && ( + + )} +
+

+ {plan.subtitle} +

+

+ {plan.price[currency]} + + { ' ' } / month + +

+ +
    + {plan.points.map((item, index) => ( +
  • + + + + +
  • + ))} +
+
+ ); + })} +
+
+ *An operation is each time you interact with your database, no matter + the compute time. +
+ We count the Prisma ORM queries you make, not the SQL statements you + run.{" "} + + Read more + {" "} + about our pricing model. +
+

+ All quotas and limits are shared across all databases in your account. +

+
+
+ + ); +} diff --git a/apps/site/src/app/pricing/pricing-page-content.tsx b/apps/site/src/app/pricing/pricing-page-content.tsx new file mode 100644 index 0000000000..8d9103deee --- /dev/null +++ b/apps/site/src/app/pricing/pricing-page-content.tsx @@ -0,0 +1,20 @@ +"use client"; + +import * as React from "react"; +import type { Symbol } from "./pricing-data"; +import { PricingCalculator } from "./pricing-calculator"; +import { PricingHeroPlans } from "./pricing-hero-plans"; + +export function PricingPageContent() { + const [currency, setCurrency] = React.useState("USD"); + + return ( + <> + + +
+ +
+ + ); +}