From 4092511f22e378eb3f64638e74a76dc1c381272d Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Fri, 19 Jun 2026 16:09:43 -0700 Subject: [PATCH] Add commercial use upsell banners --- .../components/CloneGitRepositoryDialog.tsx | 5 + .../components/CommercialUseBanner.tsx | 79 ++++++++++++++++ .../components/ExportDataDialog.tsx | 11 ++- .../components/ImportDataDialog.tsx | 5 + .../Settings/SettingsCertificates.tsx | 5 + .../components/Settings/SettingsGeneral.tsx | 8 +- .../components/Settings/SettingsProxy.tsx | 4 + .../components/core/DismissibleBanner.tsx | 91 ++++++++++++------- .../components/git/GitCommitDialog.tsx | 6 +- packages/theme/src/window.ts | 7 +- scripts/run-dev.mjs | 3 + 11 files changed, 184 insertions(+), 40 deletions(-) create mode 100644 apps/yaak-client/components/CommercialUseBanner.tsx diff --git a/apps/yaak-client/components/CloneGitRepositoryDialog.tsx b/apps/yaak-client/components/CloneGitRepositoryDialog.tsx index 3b3e2af2..32e9606d 100644 --- a/apps/yaak-client/components/CloneGitRepositoryDialog.tsx +++ b/apps/yaak-client/components/CloneGitRepositoryDialog.tsx @@ -4,6 +4,7 @@ import { Banner, VStack } from "@yaakapp-internal/ui"; import { useState } from "react"; import { openWorkspaceFromSyncDir } from "../commands/openWorkspaceFromSyncDir"; import { appInfo } from "../lib/appInfo"; +import { CommercialUseBanner } from "./CommercialUseBanner"; import { showErrorToast } from "../lib/toast"; import { Button } from "./core/Button"; import { Checkbox } from "./core/Checkbox"; @@ -89,6 +90,10 @@ export function CloneGitRepositoryDialog({ hide }: Props) { )} + + A Yaak license is required for commercial use and helps support features like this. + + { + let canceled = false; + + shouldShowCommercialUsePrompt() + .then((shouldShow) => { + if (!canceled) setVisible(shouldShow); + }) + .catch(console.error); + + return () => { + canceled = true; + }; + }, [source]); + + if (!visible) return null; + + return ( +
+ { + openCommercialUsePricing(source).catch(console.error); + }, + }, + ]} + > +
+

{title}

+

{children}

+
+
+
+ ); +} + +async function shouldShowCommercialUsePrompt(): Promise { + // Open-source builds omit the Rust license plugin, so never show commercial-use prompts there. + if (appInfo.featureLicense !== true) { + return false; + } + + try { + const license = await invoke("plugin:yaak-license|check"); + return license.status !== "active" && license.status !== "trialing"; + } catch (err) { + console.log("Failed to check license before commercial-use prompt", err); + return true; + } +} + +async function openCommercialUsePricing(source: string): Promise { + await openUrl(`https://yaak.app/pricing?s=${source}&ref=app.yaak.desktop`).catch(console.error); +} diff --git a/apps/yaak-client/components/ExportDataDialog.tsx b/apps/yaak-client/components/ExportDataDialog.tsx index fe5263a5..1bde655b 100644 --- a/apps/yaak-client/components/ExportDataDialog.tsx +++ b/apps/yaak-client/components/ExportDataDialog.tsx @@ -8,6 +8,7 @@ import slugify from "slugify"; import { activeWorkspaceAtom } from "../hooks/useActiveWorkspace"; import { pluralizeCount } from "../lib/pluralize"; import { invokeCmd } from "../lib/tauri"; +import { CommercialUseBanner } from "./CommercialUseBanner"; import { Button } from "./core/Button"; import { Checkbox } from "./core/Checkbox"; import { DetailsBanner } from "./core/DetailsBanner"; @@ -85,8 +86,12 @@ function ExportDataDialogContent({ const numSelected = Object.values(selectedWorkspaces).filter(Boolean).length; const noneSelected = numSelected === 0; return ( -
+
+ + A Yaak license is required for commercial use and helps support features like this. + + @@ -137,9 +142,9 @@ function ExportDataDialogContent({ /> -
+
- + Create Run Button
diff --git a/apps/yaak-client/components/ImportDataDialog.tsx b/apps/yaak-client/components/ImportDataDialog.tsx index 4c676c1c..347edc6f 100644 --- a/apps/yaak-client/components/ImportDataDialog.tsx +++ b/apps/yaak-client/components/ImportDataDialog.tsx @@ -1,6 +1,7 @@ import { VStack } from "@yaakapp-internal/ui"; import { useState } from "react"; import { useLocalStorage } from "react-use"; +import { CommercialUseBanner } from "./CommercialUseBanner"; import { Button } from "./core/Button"; import { SelectFile } from "./SelectFile"; @@ -14,6 +15,10 @@ export function ImportDataDialog({ importData }: Props) { return ( + + A Yaak license is required for commercial use and helps support features like this. + +
  • OpenAPI 3.0, 3.1
  • diff --git a/apps/yaak-client/components/Settings/SettingsCertificates.tsx b/apps/yaak-client/components/Settings/SettingsCertificates.tsx index b42b2593..f1b16c5c 100644 --- a/apps/yaak-client/components/Settings/SettingsCertificates.tsx +++ b/apps/yaak-client/components/Settings/SettingsCertificates.tsx @@ -4,6 +4,7 @@ import { Heading, HStack, InlineCode, VStack } from "@yaakapp-internal/ui"; import { useAtomValue } from "jotai"; import { useRef } from "react"; import { showConfirmDelete } from "../../lib/confirm"; +import { CommercialUseBanner } from "../CommercialUseBanner"; import { Button } from "../core/Button"; import { Checkbox } from "../core/Checkbox"; import { DetailsBanner } from "../core/DetailsBanner"; @@ -232,6 +233,10 @@ export function SettingsCertificates() { + + A Yaak license is required for commercial use and helps support features like this. + + {certificates.length > 0 && ( {certificates.map((cert, index) => ( diff --git a/apps/yaak-client/components/Settings/SettingsGeneral.tsx b/apps/yaak-client/components/Settings/SettingsGeneral.tsx index 42220d7b..cd9d36fc 100644 --- a/apps/yaak-client/components/Settings/SettingsGeneral.tsx +++ b/apps/yaak-client/components/Settings/SettingsGeneral.tsx @@ -14,6 +14,7 @@ import { } from "../../lib/requestSettings"; import { revealInFinderText } from "../../lib/reveal"; import { CargoFeature } from "../CargoFeature"; +import { CommercialUseBanner } from "../CommercialUseBanner"; import { IconButton } from "../core/IconButton"; import { ModelSettingRowBoolean, @@ -38,10 +39,15 @@ export function SettingsGeneral() { return ( -
    +
    General

    Configure general settings for update behavior and more.

    +
    + + A Yaak license is required for commercial use and helps support future development. + +
    diff --git a/apps/yaak-client/components/Settings/SettingsProxy.tsx b/apps/yaak-client/components/Settings/SettingsProxy.tsx index 413af214..c275d890 100644 --- a/apps/yaak-client/components/Settings/SettingsProxy.tsx +++ b/apps/yaak-client/components/Settings/SettingsProxy.tsx @@ -2,6 +2,7 @@ import { patchModel, settingsAtom } from "@yaakapp-internal/models"; import type { ProxySetting } from "@yaakapp-internal/models"; import { Heading, InlineCode, VStack } from "@yaakapp-internal/ui"; import { useAtomValue } from "jotai"; +import { CommercialUseBanner } from "../CommercialUseBanner"; import { SettingRowBoolean, SettingRowSelect, @@ -33,6 +34,9 @@ export function SettingsProxy() { traffic, or routing through specific infrastructure.

    + + A Yaak license is required for commercial use and helps support features like this. + void; color?: Color }[]; + dismissForDays?: number; + actions?: { + label: string; + onClick: () => void; + color?: Color; + variant?: ButtonProps["variant"]; + }[]; }) { - const { set: setDismissed, value: dismissed } = useKeyValue({ + const { + isLoading, + set: setDismissed, + value: dismissed, + } = useKeyValue({ namespace: "global", key: ["dismiss-banner", id], fallback: false, }); - if (dismissed) return null; + if (isLoading || isDismissed(dismissed, dismissForDays)) return null; return ( - - {children} - - {actions?.map((a) => ( - - ))} - - + +
    +
    + {children} +
    + + {actions?.map((a) => ( + + ))} +
    +
    +
    ); } + +function isDismissed( + dismissed: boolean | string | null, + dismissForDays: number | undefined, +): boolean { + if (dismissed === false || dismissed == null) return false; + if (dismissed === true) return true; + if (dismissForDays == null) return dismissed.length > 0; + + const dismissedAt = new Date(dismissed).getTime(); + if (Number.isNaN(dismissedAt)) return false; + + return Date.now() - dismissedAt < dismissForDays * 24 * 60 * 60 * 1000; +} diff --git a/apps/yaak-client/components/git/GitCommitDialog.tsx b/apps/yaak-client/components/git/GitCommitDialog.tsx index fd1b65cf..867a042b 100644 --- a/apps/yaak-client/components/git/GitCommitDialog.tsx +++ b/apps/yaak-client/components/git/GitCommitDialog.tsx @@ -16,6 +16,7 @@ import { resolvedModelName } from "../../lib/resolvedModelName"; import { showConfirm } from "../../lib/confirm"; import { showErrorToast } from "../../lib/toast"; import { sync } from "../../init/sync"; +import { CommercialUseBanner } from "../CommercialUseBanner"; import { Button } from "../core/Button"; import type { CheckboxProps } from "../core/Checkbox"; import { Checkbox } from "../core/Checkbox"; @@ -205,7 +206,10 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) { layout="horizontal" defaultRatio={0.6} firstSlot={({ style }) => ( -
    +
    + + A Yaak license is required for commercial use and helps support features like this. + { if (color == null) return {}; return { - text: color.lift(0.8).css(), - textSubtle: color.translucify(0.3).css(), - textSubtlest: color.translucify(0.6).css(), + text: color.desaturate(0.5).lift(0.12).css(), + textSubtle: color.desaturate(0.58).lift(0.04).translucify(0.04).css(), + textSubtlest: color.desaturate(0.65).translucify(0.18).css(), surface: color.translucify(0.95).css(), + surfaceHighlight: color.translucify(0.85).css(), border: color.lift(0.3).translucify(0.8).css(), }; } diff --git a/scripts/run-dev.mjs b/scripts/run-dev.mjs index 5eac2210..86a8c69c 100644 --- a/scripts/run-dev.mjs +++ b/scripts/run-dev.mjs @@ -69,6 +69,9 @@ const config = JSON.stringify({ const normalizedAdditionalArgs = []; for (let i = 0; i < additionalArgs.length; i++) { const arg = additionalArgs[i]; + if (arg === "--") { + continue; + } if (arg === "--config" && i + 1 < additionalArgs.length) { const value = additionalArgs[i + 1]; const isInlineJson = value.trimStart().startsWith("{");