snapshot of https://github.com/mountain-loop/yaak.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Lint and Test / Lint/Test (push) Has been cancelled
Generate Sponsors README / deploy (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-23 17:39:41 +05:00
commit 906fb231cc
1544 changed files with 164131 additions and 0 deletions

View File

@@ -0,0 +1,129 @@
import { checkLicense } from "@yaakapp-internal/license";
import { useCallback, useEffect, useRef, useState } from "react";
import { useKeyValue } from "../hooks/useKeyValue";
import { appInfo } from "../lib/appInfo";
import { pricingUrl } from "../lib/pricingUrl";
import { DismissibleBanner } from "./core/DismissibleBanner";
import { platform } from "@yaakapp-internal/platform";
const COMMERCIAL_USE_SNOOZE_MS = 7 * 24 * 60 * 60 * 1000;
const COMMERCIAL_USE_BANNER_MESSAGE =
"Personal use of Yaak is free. If you’re using Yaak at work, please purchase a license.";
export function CommercialUseBanner({
source,
title,
}: {
source: string;
title: string;
}) {
const [visible, setVisible] = useState(false);
const snoozeStartedRef = useRef(false);
const {
isLoading: isSnoozeLoading,
set: setSnoozedAt,
value: snoozedAt,
} = useKeyValue<string | null>({
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 && !snoozeStartedRef.current)) {
return null;
}
return (
<div className="w-full">
<DismissibleBanner
id={`commercial-use:${source}`}
color="info"
className="w-full"
onDismiss={() =>
setSnoozedAt(JSON.stringify({ source, at: new Date().toISOString() }))
}
onShow={handleShow}
actions={[
{
label: "Purchase License",
color: "info",
variant: "solid",
onClick: () => {
openCommercialUsePricing(source).catch(console.error);
},
},
]}
>
<div className="text-sm">
<p className="font-semibold text-text">{title}</p>
<p className="mt-0.5 text-text-subtle">{COMMERCIAL_USE_BANNER_MESSAGE}</p>
</div>
</DismissibleBanner>
</div>
);
}
async function shouldShowCommercialUsePrompt(): Promise<boolean> {
// 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 checkLicense();
return license.status === "personal_use";
} catch (err) {
console.log("Failed to check license before commercial-use prompt", err);
return false;
}
}
async function openCommercialUsePricing(source: string): Promise<void> {
await platform.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;
}