Repository navigation
feat(marketing): pricing page and copy improvements #6355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
847a75f
ab4b285
2c10c8d
4d997e9
efbe259
cb30c57
b7d3d8e
6707047
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,18 +30,21 @@ export function ComparisonTable() { | |
| function DesktopTable() { | ||
| return ( | ||
| <div className="hidden md:block"> | ||
| <table className="w-full table-fixed border-collapse"> | ||
| <table className="w-full table-fixed border-separate border-spacing-0"> | ||
| <thead> | ||
| <tr className="border-b border-border"> | ||
| <th className="w-2/5 py-4 pr-4 text-left text-sm font-medium text-muted-foreground"> | ||
| <tr> | ||
| <th className="sticky top-16 z-10 w-2/5 border-b border-border bg-background py-4 pr-4 text-left text-sm font-medium text-muted-foreground"> | ||
| Features | ||
| </th> | ||
| {PRICING_TIERS.map((tier) => ( | ||
| <th | ||
| key={tier.id} | ||
| className="w-1/5 py-4 px-4 text-left text-sm font-medium text-foreground" | ||
| className="sticky top-16 z-10 w-1/5 border-b border-border bg-background py-4 px-4 text-left text-sm font-medium text-foreground" | ||
| > | ||
| {tier.name} | ||
| <span className="ml-2 font-normal text-xs text-muted-foreground"> | ||
| {tierPriceLabel(tier)} | ||
| </span> | ||
| </th> | ||
| ))} | ||
| </tr> | ||
|
|
@@ -60,6 +63,16 @@ function DesktopTable() { | |
| ); | ||
| } | ||
|
|
||
| function tierPriceLabel(tier: (typeof PRICING_TIERS)[number]): string { | ||
| if (tier.price.kind === "fixed") { | ||
| return tier.price.display; | ||
| } | ||
| if (tier.price.kind === "variable") { | ||
| return `from ${tier.price.yearly.display}/user/mo`; | ||
| } | ||
| return "Custom"; | ||
| } | ||
|
Comment on lines
+66
to
+74
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Include the yearly billing cadence in the comparison header.
If the table is annual-only, include the yearly billing qualifier or render the annual total. If it must follow the billing selection, pass that selection instead of hardcoding 🤖 Prompt for AI Agents |
||
|
|
||
| function DesktopSectionGroup({ | ||
| title, | ||
| children, | ||
|
|
@@ -84,8 +97,8 @@ function DesktopSectionGroup({ | |
|
|
||
| function DesktopRow({ row }: { row: ComparisonRow }) { | ||
| return ( | ||
| <tr className="border-b border-border/60"> | ||
| <td className="py-4 pr-4 text-sm text-foreground"> | ||
| <tr> | ||
| <td className="border-b border-border/60 py-4 pr-4 text-sm text-foreground"> | ||
| <div className="flex items-center gap-2"> | ||
| <span>{row.label}</span> | ||
| {row.badge && <RowBadge badge={row.badge} />} | ||
|
|
@@ -94,7 +107,7 @@ function DesktopRow({ row }: { row: ComparisonRow }) { | |
| {row.values.map((value, index) => ( | ||
| <td | ||
| key={`${row.label}-${index}`} | ||
| className="px-4 py-4 text-sm text-foreground" | ||
| className="border-b border-border/60 px-4 py-4 text-sm text-foreground" | ||
| > | ||
| <Cell value={value} /> | ||
| </td> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,15 +55,18 @@ export function PricingCard({ tier, isYearly }: PricingCardProps) { | |
| {cadence && <p className="text-xs text-muted-foreground">{cadence}</p>} | ||
| </div> | ||
|
|
||
| <Button asChild variant={tier.cta.variant} size="lg" className="w-full"> | ||
| {tier.cta.external ? ( | ||
| <a href={tier.cta.href} target="_blank" rel="noopener noreferrer"> | ||
| {tier.cta.label} | ||
| </a> | ||
| ) : ( | ||
| <Link href={tier.cta.href}>{tier.cta.label}</Link> | ||
| )} | ||
| </Button> | ||
| <div className="flex flex-col gap-2"> | ||
| <Button asChild variant={tier.cta.variant} size="lg" className="w-full"> | ||
| {tier.cta.external ? ( | ||
| <a href={tier.cta.href} target="_blank" rel="noopener noreferrer"> | ||
| {tier.cta.label} | ||
| </a> | ||
| ) : ( | ||
| <Link href={tier.cta.href}>{tier.cta.label}</Link> | ||
| )} | ||
| </Button> | ||
| <CtaNote note={tier.ctaNote} /> | ||
| </div> | ||
|
|
||
| <ul className="flex flex-col gap-3 border-t border-border pt-6"> | ||
| {tier.features.map((feature) => ( | ||
|
|
@@ -80,6 +83,27 @@ export function PricingCard({ tier, isYearly }: PricingCardProps) { | |
| ); | ||
| } | ||
|
|
||
| function CtaNote({ note }: { note: PricingTier["ctaNote"] }) { | ||
| if (!note) { | ||
| return <p className="h-4" aria-hidden="true" />; | ||
| } | ||
| if (note.href) { | ||
| return ( | ||
| <a | ||
| href={note.href} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="text-center text-xs text-muted-foreground underline underline-offset-2 hover:text-foreground" | ||
| > | ||
| {note.label} | ||
| </a> | ||
| ); | ||
| } | ||
| return ( | ||
| <p className="text-center text-xs text-muted-foreground">{note.label}</p> | ||
| ); | ||
| } | ||
|
Comment on lines
+86
to
+105
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Move
As per coding guidelines, use one component per file and organize components as 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
| function resolvePrice(tier: PricingTier, isYearly: boolean) { | ||
| if (tier.price.kind === "fixed") { | ||
| return { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import Image from "next/image"; | ||
|
|
||
| const STRIP_LOGOS = [ | ||
| { | ||
| name: "microsoft", | ||
| label: "Microsoft", | ||
| logo: "/logos/microsoft-wordmark.svg", | ||
| height: 18, | ||
| }, | ||
| { | ||
| name: "openai", | ||
| label: "OpenAI", | ||
| logo: "/logos/openai-wordmark.svg", | ||
| height: 18, | ||
| }, | ||
| { name: "google", label: "Google", logo: "/logos/google.svg", height: 22 }, | ||
| { | ||
| name: "vercel", | ||
| label: "Vercel", | ||
| logo: "/logos/vercel-wordmark.svg", | ||
| height: 34, | ||
| }, | ||
| { | ||
| name: "datadog", | ||
| label: "Datadog", | ||
| logo: "/logos/datadog-wordmark.svg", | ||
| height: 38, | ||
| }, | ||
| { name: "amazon", label: "Amazon", logo: "/logos/amazon.png", height: 20 }, | ||
| ]; | ||
|
|
||
| export function TrustStrip() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: New TrustStrip largely duplicates the existing TrustedBySection logo-wall component and its logo config, only with a subset of the same logos and heading. Reuse TrustedBySection (or extract shared logo data) so the logo list lives in one place and stays consistent as logos change. Prompt for AI agents |
||
| return ( | ||
| <div className="flex flex-col items-center gap-6"> | ||
| <p className="text-sm text-muted-foreground">Trusted by builders from</p> | ||
| <div className="flex flex-wrap items-center justify-center gap-x-10 gap-y-4 opacity-70"> | ||
| {STRIP_LOGOS.map((client) => ( | ||
| <Image | ||
| key={client.name} | ||
| src={client.logo} | ||
| alt={client.label} | ||
| width={160} | ||
| height={client.height} | ||
| className="object-contain grayscale brightness-0 invert" | ||
| style={{ height: client.height, width: "auto" }} | ||
| unoptimized | ||
| /> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { TrustStrip } from "./TrustStrip"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Restore the homepage claim that Remote Workspaces are available on every plan.
Line 42 removes
on every plan.FeaturesSection.tsxrenders this description directly, so the homepage no longer contains the claim required by the PR objective. Restore the phrase, or update the objective if the product requirement changed.🤖 Prompt for AI Agents