mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
feat(landing): add pricing and paid windows flow
This commit is contained in:
10
.github/workflows/prerelease.yml
vendored
10
.github/workflows/prerelease.yml
vendored
@@ -106,10 +106,6 @@ jobs:
|
||||
os_type: linux
|
||||
target: aarch64-unknown-linux-gnu
|
||||
args: "--target aarch64-unknown-linux-gnu --bundles deb,rpm"
|
||||
- platform: windows-2022
|
||||
os_type: windows
|
||||
target: x86_64-pc-windows-msvc
|
||||
args: "--target x86_64-pc-windows-msvc --bundles msi"
|
||||
|
||||
steps:
|
||||
- name: Log runner selection
|
||||
@@ -202,9 +198,6 @@ jobs:
|
||||
aarch64-unknown-linux-gnu)
|
||||
opencode_asset="opencode-linux-arm64.tar.gz"
|
||||
;;
|
||||
x86_64-pc-windows-msvc)
|
||||
opencode_asset="opencode-windows-x64-baseline.zip"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported target: ${{ matrix.target }}"
|
||||
exit 1
|
||||
@@ -243,9 +236,6 @@ jobs:
|
||||
fi
|
||||
|
||||
target_name="opencode-${{ matrix.target }}"
|
||||
if [ "${{ matrix.os_type }}" = "windows" ]; then
|
||||
target_name="${target_name}.exe"
|
||||
fi
|
||||
|
||||
mkdir -p apps/desktop/src-tauri/sidecars
|
||||
cp "$bin_path" "apps/desktop/src-tauri/sidecars/${target_name}"
|
||||
|
||||
15
.github/workflows/release-macos-aarch64.yml
vendored
15
.github/workflows/release-macos-aarch64.yml
vendored
@@ -275,10 +275,6 @@ jobs:
|
||||
os_type: linux
|
||||
target: aarch64-unknown-linux-gnu
|
||||
args: "--target aarch64-unknown-linux-gnu --bundles deb,rpm"
|
||||
- platform: windows-2022
|
||||
os_type: windows
|
||||
target: x86_64-pc-windows-msvc
|
||||
args: "--target x86_64-pc-windows-msvc --bundles msi"
|
||||
|
||||
steps:
|
||||
- name: Log runner selection
|
||||
@@ -290,11 +286,6 @@ jobs:
|
||||
RUNNER_LABEL: ${{ vars.OPENWORK_LINUX_X64_RUNNER_LABEL }}
|
||||
EFFECTIVE_RUNS_ON: ${{ matrix.target == 'x86_64-unknown-linux-gnu' && vars.OPENWORK_LINUX_X64_RUNNER_LABEL != '' && vars.OPENWORK_LINUX_X64_RUNNER_LABEL || matrix.platform }}
|
||||
|
||||
- name: Enable git long paths (Windows)
|
||||
if: matrix.os_type == 'windows'
|
||||
shell: pwsh
|
||||
run: git config --global core.longpaths true
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -404,9 +395,6 @@ jobs:
|
||||
aarch64-unknown-linux-gnu)
|
||||
opencode_asset="opencode-linux-arm64.tar.gz"
|
||||
;;
|
||||
x86_64-pc-windows-msvc)
|
||||
opencode_asset="opencode-windows-x64-baseline.zip"
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported target: ${{ matrix.target }}" >&2
|
||||
exit 1
|
||||
@@ -445,9 +433,6 @@ jobs:
|
||||
fi
|
||||
|
||||
target_name="opencode-${{ matrix.target }}"
|
||||
if [ "${{ matrix.os_type }}" = "windows" ]; then
|
||||
target_name="${target_name}.exe"
|
||||
fi
|
||||
|
||||
mkdir -p apps/desktop/src-tauri/sidecars
|
||||
cp "$bin_path" "apps/desktop/src-tauri/sidecars/${target_name}"
|
||||
|
||||
@@ -74,9 +74,6 @@ type PolarProduct = {
|
||||
prices?: PolarProductPrice[]
|
||||
}
|
||||
|
||||
const CHECKOUT_TRIAL_INTERVAL = "day"
|
||||
const CHECKOUT_TRIAL_INTERVAL_COUNT = 14
|
||||
|
||||
export type CloudWorkerAccess =
|
||||
| {
|
||||
allowed: true
|
||||
@@ -194,9 +191,6 @@ function assertPaywallConfig() {
|
||||
if (!env.polar.productId) {
|
||||
throw new Error("POLAR_PRODUCT_ID is required when POLAR_FEATURE_GATE_ENABLED=true")
|
||||
}
|
||||
if (!env.polar.benefitId) {
|
||||
throw new Error("POLAR_BENEFIT_ID is required when POLAR_FEATURE_GATE_ENABLED=true")
|
||||
}
|
||||
if (!env.polar.successUrl) {
|
||||
throw new Error("POLAR_SUCCESS_URL is required when POLAR_FEATURE_GATE_ENABLED=true")
|
||||
}
|
||||
@@ -290,9 +284,6 @@ async function createCheckoutSession(input: CloudAccessInput): Promise<string> {
|
||||
products: [env.polar.productId],
|
||||
success_url: env.polar.successUrl,
|
||||
return_url: env.polar.returnUrl,
|
||||
allow_trial: true,
|
||||
trial_interval: CHECKOUT_TRIAL_INTERVAL,
|
||||
trial_interval_count: CHECKOUT_TRIAL_INTERVAL_COUNT,
|
||||
external_customer_id: input.userId,
|
||||
customer_email: input.email,
|
||||
customer_name: input.name,
|
||||
|
||||
@@ -10,10 +10,9 @@ import { useDenFlow } from "../_providers/den-flow-provider";
|
||||
// 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 TRIAL_DAYS = 14;
|
||||
|
||||
function formatSubscriptionStatus(value: string | null | undefined) {
|
||||
if (!value) return "Trial ready";
|
||||
if (!value) return "Purchase required";
|
||||
return value
|
||||
.split(/[_\s]+/)
|
||||
.filter(Boolean)
|
||||
@@ -60,9 +59,9 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
|
||||
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: { amount: 5000, currency: "usd", recurringInterval: "month", recurringIntervalCount: 1 },
|
||||
subscription: null,
|
||||
invoices: [],
|
||||
productId: null,
|
||||
@@ -175,16 +174,16 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
|
||||
<div className="flex flex-col gap-4 lg:max-w-3xl">
|
||||
<div className="grid gap-3">
|
||||
<p className="den-eyebrow">OpenWork Cloud</p>
|
||||
<h1 className="den-title-xl max-w-[12ch]">Provision shared setups for your team.</h1>
|
||||
<h1 className="den-title-xl max-w-[12ch]">Purchase worker access before launch.</h1>
|
||||
<p className="den-copy max-w-2xl">
|
||||
Share your setup across your org, launch background agents in alpha, and prepare for team-wide provider provisioning.
|
||||
Workers are disabled by default. Add one hosted OpenWork worker for $50/month, then launch it from your dashboard.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{checkoutHref ? (
|
||||
<a href={checkoutHref} rel="noreferrer" className="den-button-primary w-full sm:w-auto">
|
||||
Start free trial
|
||||
Purchase worker — $50/month
|
||||
</a>
|
||||
) : (
|
||||
<button
|
||||
@@ -193,7 +192,7 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
|
||||
onClick={() => void refreshBilling({ includeCheckout: true, quiet: false })}
|
||||
disabled={billingBusy || billingCheckoutBusy}
|
||||
>
|
||||
Refresh trial link
|
||||
Refresh purchase link
|
||||
</button>
|
||||
)}
|
||||
<a href="https://openworklabs.com/download" className="den-button-secondary w-full sm:w-auto">
|
||||
@@ -202,9 +201,9 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3 text-sm text-[var(--dls-text-secondary)]">
|
||||
<span>{TRIAL_DAYS}-day free trial</span>
|
||||
<span>$50/month per worker</span>
|
||||
<span aria-hidden="true">•</span>
|
||||
<span>{planAmountLabel} after trial</span>
|
||||
<span>{planAmountLabel} billed monthly</span>
|
||||
<span aria-hidden="true">•</span>
|
||||
<span>{user?.email ?? "Signed in"}</span>
|
||||
</div>
|
||||
@@ -280,7 +279,7 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
|
||||
<p className="den-eyebrow">Billing status</p>
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-[var(--dls-text-primary)]">{subscriptionStatus}</h2>
|
||||
<p className="den-copy text-sm">
|
||||
{billingSummary.hasActivePlan ? "Your Cloud plan is active." : `${TRIAL_DAYS}-day free trial before billing starts.`}
|
||||
{billingSummary.hasActivePlan ? "Your worker billing is active." : "Purchase a worker to enable hosted launches."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -304,7 +303,7 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
|
||||
<div className="grid gap-3">
|
||||
{checkoutHref && !billingSummary.hasActivePlan ? (
|
||||
<a href={checkoutHref} rel="noreferrer" className="den-button-primary w-full">
|
||||
Start free trial
|
||||
Purchase worker
|
||||
</a>
|
||||
) : null}
|
||||
{billingSummary.portalUrl ? (
|
||||
@@ -329,7 +328,7 @@ export function CheckoutScreen({ customerSessionToken }: { customerSessionToken:
|
||||
</a>
|
||||
) : null}
|
||||
<span>Invoices {billingSummary.invoices.length > 0 ? `(${billingSummary.invoices.length})` : ""}</span>
|
||||
<span>Cancel anytime</span>
|
||||
<span>Monthly billing</span>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
@@ -557,8 +557,8 @@ export function DashboardScreen({ showSidebar = true }: { showSidebar?: boolean
|
||||
<p className="text-[15px] leading-relaxed text-[var(--dls-text-secondary)]">
|
||||
{billingSummary?.featureGateEnabled
|
||||
? billingSummary.hasActivePlan
|
||||
? "Your account has an active OpenWork Cloud plan."
|
||||
: "Your account needs billing before the next launch."
|
||||
? "Your account has active worker billing."
|
||||
: "Workers stay disabled until you purchase one for $50/month."
|
||||
: "Billing gates are disabled in this environment."}
|
||||
</p>
|
||||
<Link
|
||||
@@ -575,7 +575,13 @@ export function DashboardScreen({ showSidebar = true }: { showSidebar?: boolean
|
||||
<div className="rounded-[24px] border border-[var(--dls-border)] bg-[var(--dls-surface)] p-8">
|
||||
<div className="mx-auto max-w-[30rem] text-center">
|
||||
<h2 className="text-2xl font-semibold tracking-tight text-[var(--dls-text-primary)]">No workers yet</h2>
|
||||
<p className="mt-3 text-sm leading-6 text-[var(--dls-text-secondary)]">Create your first worker to unlock connection details and runtime controls.</p>
|
||||
<p className="mt-3 text-sm leading-6 text-[var(--dls-text-secondary)]">Purchase your first worker to unlock connection details and runtime controls.</p>
|
||||
<Link
|
||||
href="/checkout"
|
||||
className="mt-5 inline-flex rounded-[12px] border border-[var(--dls-border)] bg-[var(--dls-surface)] px-3 py-2 text-xs font-semibold text-[var(--dls-text-secondary)] transition hover:bg-[var(--dls-hover)] hover:text-[var(--dls-text-primary)]"
|
||||
>
|
||||
Purchase worker billing
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -653,14 +659,14 @@ export function DashboardScreen({ showSidebar = true }: { showSidebar?: boolean
|
||||
|
||||
{workersBusy ? <p className="text-xs text-[var(--dls-text-secondary)]">Loading workers...</p> : null}
|
||||
{workersError ? <p className="text-xs font-medium text-rose-600">{workersError}</p> : null}
|
||||
{workers.length === 0 && !workersBusy ? <p className="text-xs text-[var(--dls-text-secondary)]">No workers yet. Create one to get started.</p> : null}
|
||||
{workers.length === 0 && !workersBusy ? <p className="text-xs text-[var(--dls-text-secondary)]">No workers yet. Purchase one to get started.</p> : null}
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-[var(--dls-text-secondary)] md:text-sm">
|
||||
Signed in as <span className="font-medium text-[var(--dls-text-primary)]">{user.email}</span>
|
||||
<div className="mt-2 text-xs text-[var(--dls-text-secondary)]">
|
||||
{billingSummary?.featureGateEnabled && !billingSummary.hasActivePlan
|
||||
? "Billing required before the next launch."
|
||||
? "Purchase required before the next launch."
|
||||
: `${ownedWorkerCount} worker${ownedWorkerCount === 1 ? "" : "s"} in your account.`}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1299,7 +1299,7 @@ export function DenFlowProvider({ children }: { children: ReactNode }) {
|
||||
setLaunchBusy(true);
|
||||
setLaunchError(null);
|
||||
setCheckoutUrl(null);
|
||||
setLaunchStatus(options.source === "signup_auto" ? "Creating your first worker..." : "Checking subscription and launch eligibility...");
|
||||
setLaunchStatus(options.source === "signup_auto" ? "Creating your first worker..." : "Checking worker billing and launch eligibility...");
|
||||
appendEvent("info", "Launch requested", resolvedLaunchName);
|
||||
|
||||
try {
|
||||
|
||||
@@ -144,7 +144,7 @@ export function BillingDashboardScreen() {
|
||||
rel="noreferrer"
|
||||
className="rounded-full bg-gray-900 px-5 py-2.5 text-[14px] font-medium text-white transition-colors hover:bg-gray-800"
|
||||
>
|
||||
Start free trial
|
||||
Purchase worker
|
||||
</a>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
- `NEXT_PUBLIC_CAL_URL` - enterprise booking link
|
||||
- `NEXT_PUBLIC_DEN_CHECKOUT_URL` - Polar checkout URL for the Den preorder CTA
|
||||
- `NEXT_PUBLIC_WINDOWS_CHECKOUT_URL` - Polar checkout URL for the Windows support plan CTA
|
||||
- `LOOPS_API_KEY` - Loops API key for feedback/contact submissions
|
||||
- `LOOPS_TRANSACTIONAL_ID_APP_FEEDBACK` - Loops transactional template ID for app feedback emails
|
||||
- `LOOPS_INTERNAL_FEEDBACK_EMAIL` - optional override for the internal feedback recipient (defaults to `team@openworklabs.com`)
|
||||
|
||||
@@ -5,13 +5,16 @@ import { getGithubData } from "../../lib/github";
|
||||
export const metadata = {
|
||||
title: "OpenWork - Download",
|
||||
description:
|
||||
"Download OpenWork desktop for macOS, Windows, and Linux. Includes AUR install instructions and direct package downloads.",
|
||||
"Download OpenWork desktop for macOS and Linux, or purchase Windows support for binary access and updates.",
|
||||
};
|
||||
|
||||
export default async function Download() {
|
||||
const github = await getGithubData();
|
||||
const releaseLabel = github.releaseTag || "latest";
|
||||
const releaseUrl = github.releaseUrl;
|
||||
const windowsCheckoutUrl =
|
||||
process.env.NEXT_PUBLIC_WINDOWS_CHECKOUT_URL || "/pricing#windows-support";
|
||||
const windowsCheckoutExternal = /^https?:\/\//.test(windowsCheckoutUrl);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
@@ -60,7 +63,7 @@ export default async function Download() {
|
||||
className="feature-card border-violet-100 bg-violet-50/50 transition hover:border-violet-200"
|
||||
>
|
||||
<h2 className="mb-2 text-[16px] font-semibold text-gray-900">Windows</h2>
|
||||
<p className="text-[14px] text-gray-700">x64 MSI installer</p>
|
||||
<p className="text-[14px] text-gray-700">$99/year support plan with manual build delivery</p>
|
||||
</a>
|
||||
<a
|
||||
href="#linux"
|
||||
@@ -111,16 +114,31 @@ export default async function Download() {
|
||||
<section id="windows" className="py-6">
|
||||
<h2 className="mb-2 text-2xl font-bold md:text-3xl">Windows</h2>
|
||||
<p className="mb-6 text-[15px] text-gray-700">
|
||||
OpenWork for Windows is available as an x64 MSI installer.
|
||||
Windows access is sold as an annual support plan. It includes one seat,
|
||||
Windows binary access, and one year of updates.
|
||||
</p>
|
||||
<a
|
||||
href={github.installers.windows.x64}
|
||||
className="doc-button"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Download Windows x64 (.msi)
|
||||
</a>
|
||||
|
||||
<div className="feature-card max-w-2xl border-violet-100 bg-white/90 ring-1 ring-violet-100/70">
|
||||
<h3 className="mb-2 text-[18px] font-semibold text-gray-900">
|
||||
Windows support — $99/year
|
||||
</h3>
|
||||
<p className="mb-5 text-[14px] leading-7 text-gray-600">
|
||||
Purchase through Polar, then we will send your Windows build link manually in phase one.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<a
|
||||
href={windowsCheckoutUrl}
|
||||
className="doc-button"
|
||||
rel={windowsCheckoutExternal ? "noreferrer" : undefined}
|
||||
target={windowsCheckoutExternal ? "_blank" : undefined}
|
||||
>
|
||||
Purchase Windows support
|
||||
</a>
|
||||
<a href="/pricing#windows-support" className="secondary-button">
|
||||
See pricing details
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getGithubData } from "../../lib/github";
|
||||
|
||||
export const metadata = {
|
||||
title: "OpenWork — Enterprise",
|
||||
description: "Secure hosting for safe, permissioned AI employees."
|
||||
description: "Enterprise OpenWork licensing with deployment support, rollout help, and included Windows support."
|
||||
};
|
||||
|
||||
export default async function Enterprise() {
|
||||
|
||||
@@ -5,6 +5,8 @@ import { headers } from "next/headers";
|
||||
export default async function Home() {
|
||||
const github = await getGithubData();
|
||||
const cal = process.env.NEXT_PUBLIC_CAL_URL || "/enterprise#book";
|
||||
const windowsCheckoutUrl =
|
||||
process.env.NEXT_PUBLIC_WINDOWS_CHECKOUT_URL || "/download#windows-support";
|
||||
const userAgent = headers().get("user-agent")?.toLowerCase() || "";
|
||||
const isMobileVisitor = /android|iphone|ipad|ipod|mobile/.test(userAgent);
|
||||
|
||||
@@ -13,6 +15,7 @@ export default async function Home() {
|
||||
stars={github.stars}
|
||||
downloadHref={github.downloads.macos}
|
||||
callHref={cal}
|
||||
windowsCheckoutUrl={windowsCheckoutUrl}
|
||||
isMobileVisitor={isMobileVisitor}
|
||||
/>
|
||||
);
|
||||
|
||||
53
ee/apps/landing/app/pricing/page.tsx
Normal file
53
ee/apps/landing/app/pricing/page.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { LandingBackground } from "../../components/landing-background";
|
||||
import { PricingGrid } from "../../components/pricing-grid";
|
||||
import { SiteFooter } from "../../components/site-footer";
|
||||
import { SiteNav } from "../../components/site-nav";
|
||||
import { getGithubData } from "../../lib/github";
|
||||
|
||||
export const metadata = {
|
||||
title: "OpenWork — Pricing",
|
||||
description:
|
||||
"Free solo desktop usage, annual Windows support, monthly cloud workers, and enterprise licensing."
|
||||
};
|
||||
|
||||
export default async function PricingPage() {
|
||||
const github = await getGithubData();
|
||||
const callUrl = process.env.NEXT_PUBLIC_CAL_URL || "/enterprise#book";
|
||||
const windowsCheckoutUrl =
|
||||
process.env.NEXT_PUBLIC_WINDOWS_CHECKOUT_URL || "/download#windows-support";
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen overflow-hidden text-[#011627]">
|
||||
<LandingBackground />
|
||||
|
||||
<div className="relative z-10 flex min-h-screen flex-col items-center pb-3 pt-1 md:pb-4 md:pt-2">
|
||||
<div className="w-full">
|
||||
<SiteNav
|
||||
stars={github.stars}
|
||||
callUrl={callUrl}
|
||||
downloadHref={github.downloads.macos}
|
||||
active="pricing"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<main className="mx-auto flex w-full max-w-6xl flex-col gap-16 px-6 pb-24 md:gap-20 md:px-8 md:pb-28">
|
||||
<section className="max-w-4xl pt-6 md:pt-10">
|
||||
<div className="mb-4 inline-flex rounded-full border border-white/70 bg-white/80 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.24em] text-slate-500 shadow-sm">
|
||||
OpenWork pricing
|
||||
</div>
|
||||
<h1 className="mb-6 text-4xl font-medium leading-[1.05] tracking-tight md:text-5xl lg:text-6xl">
|
||||
Pricing that keeps desktop free and makes paid access explicit.
|
||||
</h1>
|
||||
<p className="max-w-3xl text-lg leading-relaxed text-slate-600 md:text-xl">
|
||||
Start solo for free. Purchase Windows support when you need it. Add hosted workers when you want cloud runtime. Talk to us for enterprise licensing.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<PricingGrid windowsCheckoutUrl={windowsCheckoutUrl} callUrl={callUrl} />
|
||||
|
||||
<SiteFooter />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -71,7 +71,8 @@ export function LandingEnterprise(props: Props) {
|
||||
Run agentic workflows through your existing gateway, with
|
||||
approved tools, clear permissions, and a rollout path your
|
||||
non-technical teams can actually use, whether you self-host in
|
||||
your own infrastructure or deploy with OpenWork.
|
||||
your own infrastructure or deploy with OpenWork. Enterprise
|
||||
licensing includes Windows support.
|
||||
</p>
|
||||
|
||||
<div className="mt-8 flex flex-col items-start gap-4 sm:flex-row sm:items-center">
|
||||
@@ -185,7 +186,8 @@ export function LandingEnterprise(props: Props) {
|
||||
<p className="text-[14px] leading-relaxed text-slate-600">
|
||||
Deploy inside your own environment or work with us on a
|
||||
managed rollout, with your gateway, MCP servers, skills, and
|
||||
internal data sources connected.
|
||||
internal data sources connected. Windows support is included in
|
||||
enterprise licensing.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
landingDemoFlows,
|
||||
landingDemoFlowTimes
|
||||
} from "./landing-demo-flows";
|
||||
import { PricingGrid } from "./pricing-grid";
|
||||
import { LandingSharePackageCard } from "./landing-share-package-card";
|
||||
import { SiteFooter } from "./site-footer";
|
||||
import { SiteNav } from "./site-nav";
|
||||
@@ -20,6 +21,7 @@ type Props = {
|
||||
stars: string;
|
||||
downloadHref: string;
|
||||
callHref: string;
|
||||
windowsCheckoutUrl: string;
|
||||
isMobileVisitor: boolean;
|
||||
};
|
||||
|
||||
@@ -70,8 +72,6 @@ export function LandingHome(props: Props) {
|
||||
[activeDemoId]
|
||||
);
|
||||
|
||||
const downloadLinkProps = externalLinkProps(props.downloadHref);
|
||||
const callLinkProps = externalLinkProps(props.callHref);
|
||||
const primaryCtaHref = props.isMobileVisitor
|
||||
? "https://app.openworklabs.com"
|
||||
: "/download";
|
||||
@@ -108,6 +108,14 @@ export function LandingHome(props: Props) {
|
||||
own keys, and share your setups seamlessly with your team.
|
||||
</p>
|
||||
|
||||
<div className="mb-6 flex flex-wrap items-center gap-x-3 gap-y-2 text-[13px] font-medium text-gray-500 md:text-[14px]">
|
||||
<span>Solo free forever</span>
|
||||
<span aria-hidden="true">•</span>
|
||||
<span>Windows support $99/year</span>
|
||||
<span aria-hidden="true">•</span>
|
||||
<span>Workers $50/month</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex flex-col items-start gap-4 sm:flex-row sm:items-center">
|
||||
<div className="flex w-full flex-col gap-3 sm:w-auto sm:flex-row">
|
||||
<a
|
||||
@@ -118,11 +126,10 @@ export function LandingHome(props: Props) {
|
||||
{primaryCtaLabel} <Download size={18} />
|
||||
</a>
|
||||
<a
|
||||
href={props.callHref}
|
||||
href="/pricing"
|
||||
className="secondary-button"
|
||||
{...callLinkProps}
|
||||
>
|
||||
Contact sales
|
||||
See pricing
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -247,8 +254,8 @@ export function LandingHome(props: Props) {
|
||||
</div>
|
||||
<h2 className="mb-3 text-xl font-medium tracking-tight">Local AI worker</h2>
|
||||
<p className="mb-6 flex-1 text-sm leading-relaxed text-gray-500">
|
||||
Start free on desktop with no signup, then automate email, Slack,
|
||||
and the work you do every day.
|
||||
Start free on desktop forever with macOS and Linux downloads,
|
||||
then automate email, Slack, and the work you do every day.
|
||||
</p>
|
||||
<div>
|
||||
<a href="/download" className="secondary-button">Learn more</a>
|
||||
@@ -262,8 +269,8 @@ export function LandingHome(props: Props) {
|
||||
</div>
|
||||
<h2 className="mb-3 text-xl font-medium tracking-tight">Hosted sandboxed workers</h2>
|
||||
<p className="mb-6 flex-1 text-sm leading-relaxed text-gray-500">
|
||||
Share your setup with your team and run your local setup in the
|
||||
cloud.
|
||||
Workers are disabled by default. Purchase one for $50/month when
|
||||
you need hosted runtime.
|
||||
</p>
|
||||
<div>
|
||||
<a href="/den" className="secondary-button">Learn more</a>
|
||||
@@ -277,8 +284,8 @@ export function LandingHome(props: Props) {
|
||||
</div>
|
||||
<h2 className="mb-3 text-xl font-medium tracking-tight">Safe workflow sharing</h2>
|
||||
<p className="mb-6 flex-1 text-sm leading-relaxed text-gray-500">
|
||||
Deploy on your infrastructure. Full audit trails, admin controls,
|
||||
and data that never leaves your network.
|
||||
Deploy on your infrastructure. Enterprise licensing includes
|
||||
Windows support plus rollout guidance.
|
||||
</p>
|
||||
<div>
|
||||
<a href="/enterprise" className="secondary-button">Learn more</a>
|
||||
@@ -286,6 +293,14 @@ export function LandingHome(props: Props) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="landing-shell rounded-[2.5rem] p-8 md:p-12">
|
||||
<PricingGrid
|
||||
windowsCheckoutUrl={props.windowsCheckoutUrl}
|
||||
callUrl={props.callHref}
|
||||
showHeader={true}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="landing-shell-soft rounded-[2.5rem] p-8 md:p-12">
|
||||
<div className="mb-4 flex items-center gap-2.5 text-[11px] font-semibold uppercase tracking-[0.2em] text-gray-400">
|
||||
<PlugZap size={18} />
|
||||
|
||||
222
ee/apps/landing/components/pricing-grid.tsx
Normal file
222
ee/apps/landing/components/pricing-grid.tsx
Normal file
@@ -0,0 +1,222 @@
|
||||
import { ArrowUpRight, Cloud, Download, Monitor, Shield } from "lucide-react";
|
||||
|
||||
type PricingGridProps = {
|
||||
windowsCheckoutUrl: string;
|
||||
callUrl: string;
|
||||
showHeader?: boolean;
|
||||
};
|
||||
|
||||
type PricingCard = {
|
||||
id: string;
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
price: string;
|
||||
priceSub: string;
|
||||
description: string;
|
||||
ctaLabel: string;
|
||||
href: string;
|
||||
external?: boolean;
|
||||
features: string[];
|
||||
footer: string;
|
||||
icon: typeof Download;
|
||||
accent: string;
|
||||
};
|
||||
|
||||
function PricingAction(props: { href: string; label: string; external?: boolean }) {
|
||||
return (
|
||||
<a
|
||||
href={props.href}
|
||||
{...(props.external ? { rel: "noreferrer", target: "_blank" as const } : {})}
|
||||
className="inline-flex w-full items-center justify-center gap-2 rounded-full bg-[#011627] px-4 py-2.5 text-sm font-medium text-white transition hover:bg-[#16293f]"
|
||||
>
|
||||
{props.label}
|
||||
<ArrowUpRight size={15} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
function PricingCardView(card: PricingCard) {
|
||||
const Icon = card.icon;
|
||||
|
||||
return (
|
||||
<article
|
||||
id={card.id}
|
||||
className="group relative flex h-full flex-col overflow-hidden rounded-[28px] border border-dotted border-gray-300/80 bg-[#efefef] p-6 transition duration-300 hover:-translate-y-1 hover:border-gray-400/80 hover:shadow-[0_28px_60px_-30px_rgba(15,23,42,0.35)]"
|
||||
>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 opacity-0 transition duration-300 group-hover:opacity-100"
|
||||
style={{ background: card.accent }}
|
||||
/>
|
||||
|
||||
<div className="relative z-10 flex h-full flex-col">
|
||||
<div className="mb-8 rounded-[22px] border border-white/70 bg-white/80 p-5 shadow-[0_8px_24px_-20px_rgba(15,23,42,0.3)] transition group-hover:border-white/20 group-hover:bg-white/10 group-hover:shadow-none">
|
||||
<div className="mb-8 flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<div className="mb-2 text-[11px] font-semibold uppercase tracking-[0.22em] text-gray-500 transition group-hover:text-white/70">
|
||||
{card.eyebrow}
|
||||
</div>
|
||||
<h3 className="text-xl font-medium tracking-tight text-[#011627] transition group-hover:text-white">
|
||||
{card.title}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-gray-200 bg-white/80 p-2.5 text-gray-600 transition group-hover:border-white/20 group-hover:bg-white/10 group-hover:text-white/90">
|
||||
<Icon size={18} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-end gap-2">
|
||||
<span className="text-3xl font-semibold tracking-tight text-[#011627] transition group-hover:text-white">
|
||||
{card.price}
|
||||
</span>
|
||||
<span className="pb-1 text-xs font-medium text-gray-500 transition group-hover:text-white/70">
|
||||
{card.priceSub}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="mt-4 text-sm leading-relaxed text-gray-600 transition group-hover:text-white/80">
|
||||
{card.description}
|
||||
</p>
|
||||
|
||||
<div className="mt-6">
|
||||
<PricingAction href={card.href} label={card.ctaLabel} external={card.external} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="mb-3 inline-flex items-center gap-1.5 border-b border-dotted border-gray-300/90 pb-3 text-[12px] font-medium text-gray-500 transition group-hover:border-white/20 group-hover:text-white/70">
|
||||
Included
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col">
|
||||
{card.features.map((feature) => (
|
||||
<div
|
||||
key={feature}
|
||||
className="border-b border-dotted border-gray-300/80 py-3 text-[13px] font-medium text-gray-700 transition last:border-b-0 group-hover:border-white/15 group-hover:text-white/88"
|
||||
>
|
||||
{feature}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-sm font-medium text-gray-700 transition group-hover:text-white/88">
|
||||
{card.footer}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export function PricingGrid(props: PricingGridProps) {
|
||||
const cards: PricingCard[] = [
|
||||
{
|
||||
id: "solo",
|
||||
eyebrow: "Solo",
|
||||
title: "Free forever",
|
||||
price: "$0",
|
||||
priceSub: "open source",
|
||||
description:
|
||||
"Start on desktop for free with macOS and Linux downloads, local models, and bring-your-own-provider workflows.",
|
||||
ctaLabel: "Download free",
|
||||
href: "/download",
|
||||
features: [
|
||||
"Open source desktop app",
|
||||
"macOS and Linux downloads",
|
||||
"Bring your own keys and local models"
|
||||
],
|
||||
footer: "Best for individual builders and local-first workflows.",
|
||||
icon: Download,
|
||||
accent: "linear-gradient(135deg, #4b5563 0%, #111827 100%)"
|
||||
},
|
||||
{
|
||||
id: "windows-support",
|
||||
eyebrow: "Windows",
|
||||
title: "Windows support",
|
||||
price: "$99",
|
||||
priceSub: "per year · 1 seat",
|
||||
description:
|
||||
"Annual Windows access includes the binary plus one year of updates. In phase one we send the build link manually after purchase.",
|
||||
ctaLabel: "Purchase Windows support",
|
||||
href: props.windowsCheckoutUrl,
|
||||
external: /^https?:\/\//.test(props.windowsCheckoutUrl),
|
||||
features: [
|
||||
"1 Windows seat",
|
||||
"Binary access",
|
||||
"1 year of updates"
|
||||
],
|
||||
footer: "Manual fulfillment first, hosted delivery later.",
|
||||
icon: Monitor,
|
||||
accent: "linear-gradient(135deg, #7c3aed 0%, #1f2937 100%)"
|
||||
},
|
||||
{
|
||||
id: "cloud-workers",
|
||||
eyebrow: "Cloud workers",
|
||||
title: "One worker at a time",
|
||||
price: "$50",
|
||||
priceSub: "per month · per worker",
|
||||
description:
|
||||
"Workers stay disabled by default. Buy worker access when you want a hosted OpenWork worker for your account.",
|
||||
ctaLabel: "Purchase worker",
|
||||
href: "https://app.openworklabs.com/checkout",
|
||||
external: true,
|
||||
features: [
|
||||
"Hosted OpenWork worker",
|
||||
"Monthly billing",
|
||||
"Purchase required before launch"
|
||||
],
|
||||
footer: "Designed for cloud usage without forcing hosted billing on solo desktop users.",
|
||||
icon: Cloud,
|
||||
accent: "linear-gradient(135deg, #2563eb 0%, #0f172a 100%)"
|
||||
},
|
||||
{
|
||||
id: "enterprise-license",
|
||||
eyebrow: "Enterprise",
|
||||
title: "Talk to us",
|
||||
price: "Custom",
|
||||
priceSub: "licensing",
|
||||
description:
|
||||
"Enterprise licensing includes Windows support, rollout help, and managed or self-hosted deployment paths for larger teams.",
|
||||
ctaLabel: "Talk to us",
|
||||
href: props.callUrl,
|
||||
external: /^https?:\/\//.test(props.callUrl),
|
||||
features: [
|
||||
"Includes Windows support",
|
||||
"Deployment and rollout guidance",
|
||||
"Custom commercial terms"
|
||||
],
|
||||
footer: "Use this when you need org-wide rollout, controls, or custom terms.",
|
||||
icon: Shield,
|
||||
accent: "linear-gradient(135deg, #fb923c 0%, #111827 100%)"
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="grid gap-8">
|
||||
{props.showHeader !== false ? (
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
|
||||
<div className="max-w-3xl">
|
||||
<div className="mb-3 text-[11px] font-semibold uppercase tracking-[0.24em] text-gray-500">
|
||||
Pricing
|
||||
</div>
|
||||
<h2 className="text-3xl font-medium leading-[1.1] tracking-tight text-[#011627] md:text-4xl lg:text-5xl">
|
||||
Gray by default. Clear when you hover.
|
||||
</h2>
|
||||
</div>
|
||||
<p className="max-w-xl text-sm leading-7 text-gray-600 md:text-right md:text-base">
|
||||
Solo stays free forever. Windows is annual. Cloud workers are monthly. Enterprise starts with a conversation.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="grid gap-6 xl:grid-cols-4 md:grid-cols-2">
|
||||
{cards.map((card) => (
|
||||
<PricingCardView key={card.id} {...card} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<p className="text-center text-[12px] font-medium text-gray-500">
|
||||
Prices exclude taxes. Windows delivery is manual in phase one.
|
||||
</p>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -21,6 +21,9 @@ export function SiteFooter() {
|
||||
<Link href="/docs" target="_blank" className="transition-colors hover:text-gray-800">
|
||||
Docs
|
||||
</Link>
|
||||
<Link href="/pricing" className="transition-colors hover:text-gray-800">
|
||||
Pricing
|
||||
</Link>
|
||||
<Link href="/download" className="transition-colors hover:text-gray-800">
|
||||
Desktop
|
||||
</Link>
|
||||
|
||||
@@ -11,7 +11,7 @@ type Props = {
|
||||
downloadHref?: string;
|
||||
mobilePrimaryHref?: string;
|
||||
mobilePrimaryLabel?: string;
|
||||
active?: "home" | "download" | "enterprise" | "den" | "docs";
|
||||
active?: "home" | "pricing" | "download" | "enterprise" | "den" | "docs";
|
||||
};
|
||||
|
||||
export function SiteNav(props: Props) {
|
||||
@@ -33,6 +33,7 @@ export function SiteNav(props: Props) {
|
||||
const mobilePrimaryExternal = /^https?:\/\//.test(mobilePrimaryHref);
|
||||
const navItems = [
|
||||
{ href: "/docs", label: "Docs", key: "docs" },
|
||||
{ href: "/pricing", label: "Pricing", key: "pricing" },
|
||||
{ href: "/download", label: "Desktop", key: "download" },
|
||||
{ href: "https://app.openworklabs.com", label: "Cloud", key: "den" },
|
||||
{ href: "/enterprise", label: "Enterprise", key: "enterprise" }
|
||||
|
||||
@@ -105,13 +105,10 @@ export const getGithubData = async () => {
|
||||
const assets = Array.isArray(pick?.assets) ? pick.assets : [];
|
||||
const releaseUrl = pick?.html_url || FALLBACK_RELEASE;
|
||||
const dmg = selectAsset(assets, [".dmg"]);
|
||||
const exe = selectAsset(assets, [".exe", ".msi"], ["win", "windows"]);
|
||||
const appImage = selectAsset(assets, [".appimage"], ["linux"]);
|
||||
|
||||
const macosApple = selectAsset(assets, [".dmg"], ["darwin-aarch64"]);
|
||||
const macosIntel = selectAsset(assets, [".dmg"], ["darwin-x64"]);
|
||||
const windowsX64 =
|
||||
selectAsset(assets, [".msi", ".exe"], ["windows-x64"]) || exe;
|
||||
|
||||
const linuxDebX64 = selectAsset(assets, [".deb"], ["linux-amd64", "linux-x64"]);
|
||||
const linuxDebArm64 = selectAsset(assets, [".deb"], ["linux-arm64", "linux-aarch64"]);
|
||||
@@ -124,7 +121,6 @@ export const getGithubData = async () => {
|
||||
releaseTag: pick?.tag_name || "",
|
||||
downloads: {
|
||||
macos: dmg?.browser_download_url || FALLBACK_RELEASE,
|
||||
windows: exe?.browser_download_url || FALLBACK_RELEASE,
|
||||
linux:
|
||||
appImage?.browser_download_url ||
|
||||
linuxDebX64?.browser_download_url ||
|
||||
@@ -136,9 +132,6 @@ export const getGithubData = async () => {
|
||||
appleSilicon: macosApple?.browser_download_url || dmg?.browser_download_url || releaseUrl,
|
||||
intel: macosIntel?.browser_download_url || dmg?.browser_download_url || releaseUrl
|
||||
},
|
||||
windows: {
|
||||
x64: windowsX64?.browser_download_url || releaseUrl
|
||||
},
|
||||
linux: {
|
||||
aur: "https://aur.archlinux.org/packages/openwork",
|
||||
debX64: linuxDebX64?.browser_download_url || releaseUrl,
|
||||
|
||||
Reference in New Issue
Block a user