diff --git a/apps/yaak-client/components/CloneGitRepositoryDialog.tsx b/apps/yaak-client/components/CloneGitRepositoryDialog.tsx index 3b3e2af2..ccf14580 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,8 @@ export function CloneGitRepositoryDialog({ hide }: Props) { )} + + ({ + namespace: "global", + key: "commercial-use-banner-snoozed-at", + fallback: null, + }); + + useEffect(() => { + let canceled = false; + + shouldShowCommercialUsePrompt() + .then((shouldShow) => { + if (!canceled) setVisible(shouldShow); + }) + .catch(console.error); + + return () => { + canceled = true; + }; + }, [source]); + + const snoozed = isSnoozed(snoozedAt, COMMERCIAL_USE_SNOOZE_MS); + const handleShow = useCallback(() => { + if (snoozeStartedRef.current || snoozed) { + return; + } + + snoozeStartedRef.current = true; + setSnoozedAt(JSON.stringify({ source, at: new Date().toISOString() })).catch(console.error); + }, [setSnoozedAt, snoozed, source]); + + if (!visible || isSnoozeLoading || snoozed) { + return null; + } + + return ( + + + setSnoozedAt(JSON.stringify({ source, at: new Date().toISOString() })) + } + onShow={handleShow} + actions={[ + { + label: "View plans", + color: "info", + variant: "solid", + onClick: () => { + openCommercialUsePricing(source).catch(console.error); + }, + }, + ]} + > + + {title} + {COMMERCIAL_USE_BANNER_MESSAGE} + + + + ); +} + +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(pricingUrl(`app.commercial-use.${source}`)).catch(console.error); +} + +function isSnoozed(value: string | null, ms: number): boolean { + if (value == null) return false; + + try { + const snooze = JSON.parse(value) as { at?: unknown }; + const at = typeof snooze.at === "string" ? snooze.at : null; + return isWithinMs(at, ms); + } catch { + // Older builds stored only the timestamp, so keep respecting that as a global snooze. + return isWithinMs(value, ms); + } +} + +function isWithinMs(date: string | null, ms: number): boolean { + if (date == null) return false; + + const time = new Date(date).getTime(); + if (Number.isNaN(time)) return false; + + return Date.now() - time < ms; +} diff --git a/apps/yaak-client/components/ExportDataDialog.tsx b/apps/yaak-client/components/ExportDataDialog.tsx index fe5263a5..620182be 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,10 @@ function ExportDataDialogContent({ const numSelected = Object.values(selectedWorkspaces).filter(Boolean).length; const noneSelected = numSelected === 0; return ( - + + + @@ -137,9 +140,9 @@ function ExportDataDialogContent({ /> -
{title}
{COMMERCIAL_USE_BANNER_MESSAGE}