From 1721bb6054e39875ac3284fe35e3fa4803a52997 Mon Sep 17 00:00:00 2001 From: shizhigu Date: Sat, 25 Apr 2026 21:29:36 -0500 Subject: [PATCH 1/7] Polish Den web billing UI --- .../app/(den)/_components/auth-panel.tsx | 2 +- .../app/(den)/_components/auth-screen.tsx | 2 +- .../app/(den)/_components/checkout-screen.tsx | 257 ++++++++++++------ .../(den)/_components/dashboard-screen.tsx | 2 +- .../app/(den)/_components/den-shell.tsx | 7 +- .../app/(den)/_components/ui/button.tsx | 8 +- .../den-web/app/(den)/_components/ui/card.tsx | 6 +- .../ui/dashboard-page-template.tsx | 26 +- .../_components/billing-dashboard-screen.tsx | 54 ++-- .../_components/org-dashboard-shell.tsx | 62 ++--- ee/apps/den-web/app/globals.css | 99 ++++--- .../den-web/components/den-marketing-rail.tsx | 12 +- .../checkout-billing-polish-desktop.png | Bin 0 -> 267284 bytes .../checkout-billing-polish-mobile.png | Bin 0 -> 106929 bytes 14 files changed, 334 insertions(+), 203 deletions(-) create mode 100644 ee/apps/den-web/docs/screenshots/checkout-billing-polish-desktop.png create mode 100644 ee/apps/den-web/docs/screenshots/checkout-billing-polish-mobile.png diff --git a/ee/apps/den-web/app/(den)/_components/auth-panel.tsx b/ee/apps/den-web/app/(den)/_components/auth-panel.tsx index 493702824..d4040deec 100644 --- a/ee/apps/den-web/app/(den)/_components/auth-panel.tsx +++ b/ee/apps/den-web/app/(den)/_components/auth-panel.tsx @@ -124,7 +124,7 @@ export function AuthPanel({ const resolvedSignUpContent: PanelContent = { title: "Get started.", - copy: "Free to try. Team plans from $50/mo.", + copy: "Start with desktop, then add Cloud when your team needs shared setup.", submitLabel: "Create account", togglePrompt: "Have an account?", toggleActionLabel: "Sign in", diff --git a/ee/apps/den-web/app/(den)/_components/auth-screen.tsx b/ee/apps/den-web/app/(den)/_components/auth-screen.tsx index 4bbb202ae..144327ec2 100644 --- a/ee/apps/den-web/app/(den)/_components/auth-screen.tsx +++ b/ee/apps/den-web/app/(den)/_components/auth-screen.tsx @@ -111,7 +111,7 @@ export function AuthScreen() { OpenWork Cloud -

+

One setup, every seat.

diff --git a/ee/apps/den-web/app/(den)/_components/checkout-screen.tsx b/ee/apps/den-web/app/(den)/_components/checkout-screen.tsx index f0f4eed04..016b8d8e8 100644 --- a/ee/apps/den-web/app/(den)/_components/checkout-screen.tsx +++ b/ee/apps/den-web/app/(den)/_components/checkout-screen.tsx @@ -1,15 +1,30 @@ "use client"; +import { + CheckCircle2, + CreditCard, + Download, + RefreshCw, + Server, + ShieldCheck, + Users, +} from "lucide-react"; import { usePathname, useRouter } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { isSamePathname } from "../_lib/client-route"; -import { formatMoneyMinor } from "../_lib/den-flow"; +import { formatMoneyMinor, formatRecurringInterval, type BillingPrice } from "../_lib/den-flow"; import { useDenFlow } from "../_providers/den-flow-provider"; // For local layout testing (no deploy needed) // Enable with: NEXT_PUBLIC_DEN_MOCK_BILLING=1 const MOCK_BILLING = process.env.NEXT_PUBLIC_DEN_MOCK_BILLING === "1"; const MOCK_CHECKOUT_URL = (process.env.NEXT_PUBLIC_DEN_MOCK_CHECKOUT_URL ?? "").trim() || null; +const MOCK_PRICE: BillingPrice = { + amount: 5000, + currency: "usd", + recurringInterval: "month", + recurringIntervalCount: 1, +}; function formatSubscriptionStatus(value: string | null | undefined) { if (!value) return "Purchase required"; @@ -24,13 +39,54 @@ function LoadingPanel({ title, body }: { title: string; body: string }) { return (

-

{title}

+

{title}

{body}

); } +function getPlanLabels(price: BillingPrice | null) { + if (!price || price.amount === null) { + return { + amount: "Plan price unavailable", + cadence: "billing cycle", + inline: "Plan price unavailable", + }; + } + + const amount = formatMoneyMinor(price.amount, price.currency); + const cadence = formatRecurringInterval(price.recurringInterval, price.recurringIntervalCount); + + return { + amount, + cadence, + inline: `${amount} ${cadence}`, + }; +} + +function FeatureLine({ + icon: Icon, + title, + body, +}: { + icon: typeof CheckCircle2; + title: string; + body: string; +}) { + return ( +
+
+
+
+

{title}

+

{body}

+
+
+ ); +} + export function CheckoutScreen({ customerSessionToken }: { customerSessionToken: string | null }) { const router = useRouter(); const pathname = usePathname(); @@ -54,14 +110,14 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken: const mockMode = MOCK_BILLING && process.env.NODE_ENV !== "production"; - const billingSummary = MOCK_BILLING + const billingSummary = mockMode ? { featureGateEnabled: true, hasActivePlan: false, checkoutRequired: true, - checkoutUrl: MOCK_CHECKOUT_URL, - portalUrl: null, - price: { amount: 5000, currency: "usd", recurringInterval: "month", recurringIntervalCount: 1 }, + checkoutUrl: MOCK_CHECKOUT_URL, + portalUrl: null, + price: MOCK_PRICE, subscription: null, invoices: [], productId: null, @@ -182,53 +238,91 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken: } const billingPrice = billingSummary?.price ?? null; - const showLoading = resuming || (billingBusy && !billingSummary && !MOCK_BILLING); - const checkoutHref = effectiveCheckoutUrl ?? MOCK_CHECKOUT_URL ?? null; - const planAmountLabel = - billingPrice && billingPrice.amount !== null - ? `${formatMoneyMinor(billingPrice.amount, billingPrice.currency)}/${billingPrice.recurringInterval}` - : "$50.00/month"; + const showLoading = resuming || (billingBusy && !billingSummary && !mockMode); + const checkoutHref = effectiveCheckoutUrl ?? billingSummary?.checkoutUrl ?? (mockMode ? MOCK_CHECKOUT_URL : null); + const planLabels = getPlanLabels(billingPrice); const subscription = billingSummary?.subscription ?? null; - const subscriptionStatus = formatSubscriptionStatus(subscription?.status); + const hasActivePlan = Boolean(billingSummary?.hasActivePlan); + const subscriptionStatus = subscription + ? formatSubscriptionStatus(subscription.status) + : hasActivePlan + ? "Active" + : "Purchase required"; return (
-
-
-
+
+
+

OpenWork Cloud

-

Purchase a plan before creating your workspace.

+

Create your cloud workspace.

- Start with one workspace plan for $50/month. Each plan includes up to 5 members and 1 hosted worker. + Add the workspace plan to turn on hosted workers, shared team setup, and billing controls.

-
-
- {checkoutHref ? ( - - Purchase plan — $50/month +
+ {checkoutHref ? ( + + + ) : ( + + )} + + - ) : ( - - )} - - Use desktop only - +
+ +
+ One workspace plan + + {planLabels.inline} + + {user?.email ?? "Signed in"} +
-
- $50/month per workspace - - {planAmountLabel} billed monthly - - {user?.email ?? "Signed in"} +
+
+ + Workspace plan + +
+
+
+ {planLabels.amount} + {planLabels.cadence} +
+

+ Includes up to 5 members and 1 hosted worker. +

+
+
+
+
+
+
+
+
+
@@ -241,52 +335,61 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken: ) : null} {billingSummary ? ( -
-
+
+
OpenWork Cloud -

Share your setup across your team.

+

Share the setup.

- Manage your team's setup, invite teammates, and keep everything in sync. + Keep teammates on the same tools, providers, and worker setup.

-
-
Share setup across your team and org
-
Background agents in alpha for selected workflows
-
Custom LLM providers with team access controls
-
- -
-
-

Background agents

-

- Keep selected workflows running in the background. Alpha. -

-
-
-

LLM providers

-

- Standardize provider access, model selection, and team rollout. -

-
+
+ + +
Desktop app -

Stay local when you need to.

+

Stay local.

Run locally for free, keep your data on your machine, and add OpenWork Cloud when your team is ready.

-
-
Run locally for free
-
Keep data on your machine
-
Move into OpenWork Cloud later
+
+ + +
@@ -300,22 +403,22 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken: