Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
27 changes: 0 additions & 27 deletions web/app/[locale]/dashboard/billing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { localizedVaultPath, vaultSignInHref } from "@/app/lib/vault-auth";
import {
FeatureList,
PlanCard,
PrimaryLink,
visibleProFeatures,
} from "@/app/components/pricing-shared";
import {
Expand Down Expand Up @@ -291,19 +290,6 @@ function FreePlanUpsell({
annual={pricingT("perMonthBilledYearly")}
/>
}
priceDetail={
<PricingIntervalValue
monthly={null}
annual={
<p className="mt-2 text-sm text-muted">
{pricingT("annualPriceDetail", {
amount: PRO_PRICING_USD.year.billedAmount,
discount: PRO_PRICING_USD.year.discountPercent,
})}
</p>
}
/>
}
>
<PricingCheckoutButton
hrefs={proCheckoutHrefs}
Expand All @@ -329,19 +315,6 @@ function FreePlanUpsell({
annual={pricingT("perUserMonthBilledYearly")}
/>
}
priceDetail={
<PricingIntervalValue
monthly={null}
annual={
<p className="mt-2 text-sm text-muted">
{pricingT("annualPriceDetail", {
amount: TEAM_PRICING_USD.year.billedAmount,
discount: TEAM_PRICING_USD.year.discountPercent,
})}
</p>
}
/>
}
>
<PricingCheckoutButton
hrefs={teamCheckoutHrefs}
Expand Down
28 changes: 0 additions & 28 deletions web/app/[locale]/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,12 @@ export default async function PricingPage({
};
const annualComparePrice = t("annualComparePrice", {
monthly: PRO_PRICING_USD.year.monthlyEquivalent,
annual: PRO_PRICING_USD.year.billedAmount,
});
const teamMonthlyComparePrice = t("teamMonthlyComparePrice", {
monthly: TEAM_PRICING_USD.month.monthlyEquivalent,
});
const teamAnnualComparePrice = t("teamAnnualComparePrice", {
monthly: TEAM_PRICING_USD.year.monthlyEquivalent,
annual: TEAM_PRICING_USD.year.billedAmount,
});

const freeFeatures = t.raw("free.features") as string[];
Expand Down Expand Up @@ -192,19 +190,6 @@ export default async function PricingPage({
annual={t("perMonthBilledYearly")}
/>
}
priceDetail={
<PricingIntervalValue
monthly={null}
annual={
<p className="mt-2 text-sm text-muted">
{t("annualPriceDetail", {
amount: PRO_PRICING_USD.year.billedAmount,
discount: PRO_PRICING_USD.year.discountPercent,
})}
</p>
}
/>
}
badge={
snapshot.isPro ? (
<CurrentPlanBadge>{t("currentPlan")}</CurrentPlanBadge>
Expand Down Expand Up @@ -242,19 +227,6 @@ export default async function PricingPage({
annual={t("perUserMonthBilledYearly")}
/>
}
priceDetail={
<PricingIntervalValue
monthly={null}
annual={
<p className="mt-2 text-sm text-muted">
{t("annualPriceDetail", {
amount: TEAM_PRICING_USD.year.billedAmount,
discount: TEAM_PRICING_USD.year.discountPercent,
})}
</p>
}
/>
}
>
<PricingCheckoutButton
hrefs={teamCheckoutHrefs}
Expand Down
30 changes: 1 addition & 29 deletions web/app/app-pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ export default async function AppPricingPage({
const faqItems = visibleFaqItems(pricing.faq.items as FaqItem[]);
const annualComparePrice = pricingMessage(pricing.annualComparePrice, {
monthly: PRO_PRICING_USD.year.monthlyEquivalent,
annual: PRO_PRICING_USD.year.billedAmount,
});
const teamMonthlyComparePrice = pricingMessage(
pricing.teamMonthlyComparePrice,
Expand All @@ -96,7 +95,6 @@ export default async function AppPricingPage({
pricing.teamAnnualComparePrice,
{
monthly: TEAM_PRICING_USD.year.monthlyEquivalent,
annual: TEAM_PRICING_USD.year.billedAmount,
},
);

Expand Down Expand Up @@ -129,7 +127,7 @@ export default async function AppPricingPage({
surface="app_pricing"
/>

<div className="mt-6 grid items-stretch gap-5 sm:grid-cols-2 min-[1800px]:grid-cols-4">
<div className="mt-6 grid items-stretch gap-5 sm:grid-cols-2 lg:grid-cols-4">
<PlanCard
name={pricing.free.name}
price={pricing.free.price}
Expand Down Expand Up @@ -167,19 +165,6 @@ export default async function AppPricingPage({
annual={pricing.perMonthBilledYearly}
/>
}
priceDetail={
<PricingIntervalValue
monthly={null}
annual={
<p className="mt-2 text-sm text-muted">
{pricingMessage(pricing.annualPriceDetail, {
amount: PRO_PRICING_USD.year.billedAmount,
discount: PRO_PRICING_USD.year.discountPercent,
})}
</p>
}
/>
}
badge={
snapshot.isPro ? (
<CurrentPlanBadge>{pricing.currentPlan}</CurrentPlanBadge>
Expand Down Expand Up @@ -225,19 +210,6 @@ export default async function AppPricingPage({
annual={pricing.perUserMonthBilledYearly}
/>
}
priceDetail={
<PricingIntervalValue
monthly={null}
annual={
<p className="mt-2 text-sm text-muted">
{pricingMessage(pricing.annualPriceDetail, {
amount: TEAM_PRICING_USD.year.billedAmount,
discount: TEAM_PRICING_USD.year.discountPercent,
})}
</p>
}
/>
}
>
{appStorePaymentGated ? (
<DisabledButton>{pricing.billingUnavailable}</DisabledButton>
Expand Down
17 changes: 9 additions & 8 deletions web/app/components/pricing-shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,33 +66,32 @@ export function PlanCard({
name,
price,
period,
priceDetail,
badge,
children,
}: {
name: string;
price: ReactNode;
period?: ReactNode;
priceDetail?: ReactNode;
badge?: ReactNode;
children: ReactNode;
}) {
return (
<div className="relative flex h-full min-w-0 flex-col border border-border p-6">
{badge ? <div className="absolute right-6 top-6">{badge}</div> : null}
<h2 className="pr-28 text-sm font-medium tracking-tight">{name}</h2>
<div className="mt-3" style={{ minHeight: "4rem" }}>
<div className="mt-3">
<div className="flex items-baseline gap-1.5">
<span className="text-3xl font-medium tracking-tight">{price}</span>
<span className="text-3xl font-medium tabular-nums tracking-tight">
{price}
</span>
{period ? (
<span className="max-w-44 text-sm leading-snug text-muted">
{period}
</span>
) : null}
</div>
{priceDetail}
</div>
<div className="mt-5">{children}</div>
<div className="mt-3">{children}</div>
</div>
);
}
Expand Down Expand Up @@ -287,7 +286,7 @@ export function PricingSizeTable({
<td className="px-4 py-3 text-left align-top text-muted">
{row.use}
</td>
<td className="whitespace-nowrap px-4 py-3 text-left align-top">
<td className="whitespace-nowrap px-4 py-3 text-left align-top tabular-nums">
{row.rate}
</td>
</tr>
Expand All @@ -311,7 +310,9 @@ function ColumnHead({
return (
<div className="px-4 text-left align-bottom font-medium">
{name}
<span className="block text-xs font-normal text-muted">{price}</span>
<span className="block text-xs font-normal tabular-nums text-muted">
{price}
</span>
{action ? <div className="mt-2 max-w-32">{action}</div> : null}
</div>
);
Expand Down
29 changes: 14 additions & 15 deletions web/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,17 +143,17 @@
"name": "cmux Pro",
"activeBody": "Your plan renews on {date}.",
"pendingBody": "Your plan is scheduled to end on {date}.",
"monthlyPrice": "$30/month",
"annualPrice": "$288/year",
"legacyAnnualPrice": "$240/year"
"monthlyPrice": "$30/mo",
"annualPrice": "$24/mo, billed annually",
"legacyAnnualPrice": "$20/mo, billed annually"
},
"team": {
"name": "cmux Team",
"fallbackName": "your team",
"activeBody": "{team} renews on {date}.",
"pendingBody": "{team} is scheduled to end on {date}.",
"price": "$35/seat/month",
"annualPrice": "$336/seat/year"
"price": "$35/seat/mo",
"annualPrice": "$28/seat/mo, billed annually"
},
"details": {
"renewsOn": "Renews on",
Expand Down Expand Up @@ -403,18 +403,17 @@
"metaDescriptionNoVault": "cmux is a free, open source macOS terminal for AI coding agents. Upgrade to Pro for Cloud VMs and a model gateway. Enterprise adds SSO and self-hosting.",
"metaDescriptionNoVaultShort": "cmux is a free, open source macOS terminal. Pro adds Cloud VMs and a model gateway; Enterprise adds SSO and self-hosting.",
"title": "Pricing",
"perMonth": "per month",
"perMonthBilledYearly": "per month billed yearly",
"perUserMonth": "per user per month",
"perUserMonthBilledYearly": "per user per month billed yearly",
"perMonth": "/mo",
"perMonthBilledYearly": "/mo",
"perUserMonth": "/user/mo",
"perUserMonthBilledYearly": "/user/mo",
"billingPeriod": "Billing period",
"monthly": "Monthly",
"annual": "Annual",
"saveAnnual": "Save {discount}%",
"annualPriceDetail": "Billed ${amount} annually · save {discount}%",
"annualComparePrice": "${monthly}/month · ${annual}/year",
"teamMonthlyComparePrice": "${monthly}/user/month",
"teamAnnualComparePrice": "${monthly}/user/month · ${annual}/user/year",
"annualComparePrice": "${monthly}/mo",
"teamMonthlyComparePrice": "${monthly}/user/mo",
"teamAnnualComparePrice": "${monthly}/user/mo",
"currentPlan": "Current plan",
"manageBilling": "Manage billing",
"free": {
Expand Down Expand Up @@ -672,15 +671,15 @@
},
{
"q": "How does billing work?",
"a": "Pro is $30 per month, or $288 per year ($24 per month billed annually, a 20% savings), and includes 20 active compute-hours per month on the 4 vCPU / 16 GB Cloud VM. Beyond that, cloud usage is billed per active compute-hour by VM size (see Cloud VM sizes above). Idle VMs suspend automatically, and running agents in parallel just uses more hours. The terminal and local agents stay free."
"a": "Pro is $30/mo, or $24/mo when billed annually (a 20% savings), and includes 20 active compute-hours per month on the 4 vCPU / 16 GB Cloud VM. Beyond that, cloud usage is billed per active compute-hour by VM size (see Cloud VM sizes above). Idle VMs suspend automatically, and running agents in parallel just uses more hours. The terminal and local agents stay free."
},
{
"q": "Can I self-host the cloud?",
"a": "Yes, on Enterprise. Run agent execution on your own infrastructure or fully air-gapped, so source code never leaves your network."
},
{
"q": "Do you have a team plan?",
"a": "Yes. Team is $35 per user per month, or $336 per user per year ($28 per user per month billed annually): unified billing for the whole team, centralized seat management, pooled Cloud VM hours, and shared team rules. Enterprise adds SSO, self-hosting, and audit logs."
"a": "Yes. Team is $35/user/mo, or $28/user/mo when billed annually: unified billing for the whole team, centralized seat management, pooled Cloud VM hours, and shared team rules. Enterprise adds SSO, self-hosting, and audit logs."
}
]
},
Expand Down
23 changes: 11 additions & 12 deletions web/messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,16 @@
"activeBody": "プランは{date}に更新されます。",
"pendingBody": "プランは{date}に終了する予定です。",
"monthlyPrice": "$30/月",
"annualPrice": "$288/年",
"legacyAnnualPrice": "$240/年"
"annualPrice": "$24/月(年払い)",
"legacyAnnualPrice": "$20/月(年払い)"
},
"team": {
"name": "cmux Team",
"fallbackName": "チーム",
"activeBody": "{team}は{date}に更新されます。",
"pendingBody": "{team}は{date}に終了する予定です。",
"price": "$35/シート/月",
"annualPrice": "$336/シート/年"
"annualPrice": "$28/シート/月(年払い)"
},
"details": {
"renewsOn": "更新日",
Expand Down Expand Up @@ -403,18 +403,17 @@
"metaDescriptionNoVault": "cmux はAIコーディングエージェント向けの無料・オープンソースの macOS ターミナルです。Pro では Cloud VM とモデルゲートウェイが使えます。Enterprise は SSO とセルフホストに対応します。",
"metaDescriptionNoVaultShort": "cmux は無料・オープンソースの macOS ターミナルです。Pro では Cloud VM とモデルゲートウェイを利用でき、Enterprise では SSO とセルフホストに対応します。",
"title": "料金",
"perMonth": "月額",
"perMonthBilledYearly": "月額(年払い)",
"perUserMonth": "ユーザーあたり月額",
"perUserMonthBilledYearly": "ユーザーあたり月額(年払い)",
"perMonth": "/月",
"perMonthBilledYearly": "/月",
"perUserMonth": "/ユーザー/月",
"perUserMonthBilledYearly": "/ユーザー/月",
"billingPeriod": "請求期間",
"monthly": "月払い",
"annual": "年払い",
"saveAnnual": "{discount}%割引",
"annualPriceDetail": "年額 ${amount} の一括払い · {discount}%割引",
"annualComparePrice": "${monthly}/月 · ${annual}/年",
"annualComparePrice": "${monthly}/月",
"teamMonthlyComparePrice": "${monthly}/ユーザー/月",
"teamAnnualComparePrice": "${monthly}/ユーザー/月 · ${annual}/ユーザー/年",
"teamAnnualComparePrice": "${monthly}/ユーザー/月",
"currentPlan": "現在のプラン",
"manageBilling": "請求を管理",
"free": {
Expand Down Expand Up @@ -672,15 +671,15 @@
},
{
"q": "課金はどのように行われますか?",
"a": "Pro は月額 $30、または年額 $288(年払いでは月あたり $24、20%割引)で、4 vCPU / 16 GB の Cloud VM で毎月20時間のアクティブ計算時間が含まれます。それを超えると、VM サイズに応じてアクティブな計算時間あたりで課金されます(上記の Cloud VM サイズを参照)。アイドルの VM は自動的に一時停止し、エージェントを並列実行するとより多くの時間を使うだけです。ターミナルとローカルエージェントは無料のままです。"
"a": "Pro は月払いで $30/月、年払いでは $24/月(20%割引)で、4 vCPU / 16 GB の Cloud VM で毎月20時間のアクティブ計算時間が含まれます。それを超えると、VM サイズに応じてアクティブな計算時間あたりで課金されます(上記の Cloud VM サイズを参照)。アイドルの VM は自動的に一時停止し、エージェントを並列実行するとより多くの時間を使うだけです。ターミナルとローカルエージェントは無料のままです。"
},
{
"q": "クラウドをセルフホストできますか?",
"a": "はい、Enterprise で可能です。エージェントの実行を自社インフラ上で、または完全なエアギャップで動かせるため、ソースコードがネットワーク外に出ることはありません。"
},
{
"q": "チームプランはありますか?",
"a": "はい。Team は1ユーザーあたり月額 $35、または年額 $336(月額換算 $28、20%割引)です。チーム全体の一元請求、シート管理、Cloud VM 時間のプール、共有チームルールが含まれます。Enterprise では SSO、セルフホスト、監査ログが追加されます。"
"a": "はい。Team は月払いで $35/ユーザー/月、年払いでは $28/ユーザー/月です。チーム全体の一元請求、シート管理、Cloud VM 時間のプール、共有チームルールが含まれます。Enterprise では SSO、セルフホスト、監査ログが追加されます。"
}
]
},
Expand Down
22 changes: 15 additions & 7 deletions web/tests/app-pricing-page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,14 @@ describe("app pricing page", () => {
expect(html).toContain(
"http://localhost:9210/api/billing/checkout?plan=team&amp;cmux_external_browser=1&amp;cmux_scheme=cmux-dev-test",
);
expect(html).toContain("$35/user/month");
expect(html).toContain("/mo");
expect(html).toContain("/user/mo");
expect(html).not.toContain("/mo.");
expect(html).toContain("$35/user/mo");
expect(html).toContain('<p class="mt-5 text-sm font-medium">Includes:</p>');
expect(html).toContain("sm:grid-cols-2 min-[1800px]:grid-cols-4");
expect(html).not.toContain('style="min-height:4rem"');
expect(html).toContain("text-3xl font-medium tabular-nums tracking-tight");
expect(html).toContain("sm:grid-cols-2 lg:grid-cols-4");
expect(html.split("api/billing/checkout?plan=pro")).toHaveLength(2);
expect(html.split("api/billing/checkout?plan=team")).toHaveLength(2);
expect(html).toContain("Compare plans");
Expand All @@ -121,11 +126,14 @@ describe("app pricing page", () => {

expect(html).toContain("$24");
expect(html).toContain("$28");
expect(html).toContain("per month billed yearly");
expect(html).toContain("per user per month billed yearly");
expect(html).toContain("Billed $288 annually · save 20%");
expect(html).toContain("Billed $336 annually · save 20%");
expect(html).toContain("$28/user/month · $336/user/year");
expect(html).toContain("/mo");
expect(html).toContain("/user/mo");
expect(html).not.toContain("/mo.");
expect(html).not.toContain("Billed $288 annually · save 20%");
expect(html).not.toContain("Billed $336 annually · save 20%");
expect(html).toContain("$28/user/mo");
expect(html).not.toContain("$288/year");
expect(html).not.toContain("$336/user/year");
expect(html).toContain(
"http://localhost:9210/api/billing/checkout?plan=pro&amp;cmux_external_browser=1&amp;cmux_scheme=cmux-dev-test&amp;interval=year",
);
Expand Down
Loading