From af7782c93bf7fbadec3267f97271729219e1eb58 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Mon, 24 Feb 2025 05:59:15 -0800 Subject: [PATCH] Better license flows --- src-tauri/Cargo.lock | 94 +++++++++++++++---- src-tauri/Cargo.toml | 2 +- src-web/components/GitDropdown.tsx | 4 +- src-web/components/LicenseBadge.tsx | 40 +++++--- .../components/Settings/SettingsLicense.tsx | 62 ++++++++---- src-web/components/SettingsDropdown.tsx | 2 +- src-web/components/core/Dropdown.tsx | 34 ++++--- src-web/hooks/useHotKey.ts | 1 - src-web/hooks/useLicenseConfirmation.ts | 15 +++ 9 files changed, 184 insertions(+), 70 deletions(-) create mode 100644 src-web/hooks/useLicenseConfirmation.ts diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ea1796d6..003ca0f5 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -514,6 +514,25 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" +dependencies = [ + "objc-sys", +] + +[[package]] +name = "block2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58aa60e59d8dbfcc36138f5f18be5f24394d33b38b24f7fd0b1caa33095f22f" +dependencies = [ + "block-sys", + "objc2", +] + [[package]] name = "block2" version = "0.5.1" @@ -2420,6 +2439,16 @@ dependencies = [ "png", ] +[[package]] +name = "icrate" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb69199826926eb864697bddd27f73d9fddcffc004f5733131e15b465e30642" +dependencies = [ + "block2 0.4.0", + "objc2", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -3292,7 +3321,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ "bitflags 2.6.0", - "block2", + "block2 0.5.1", "libc", "objc2", "objc2-core-data", @@ -3308,7 +3337,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.6.0", - "block2", + "block2 0.5.1", "objc2", "objc2-core-location", "objc2-foundation", @@ -3320,7 +3349,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ - "block2", + "block2 0.5.1", "objc2", "objc2-foundation", ] @@ -3332,7 +3361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.6.0", - "block2", + "block2 0.5.1", "objc2", "objc2-foundation", ] @@ -3343,7 +3372,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "block2", + "block2 0.5.1", "objc2", "objc2-foundation", "objc2-metal", @@ -3355,7 +3384,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ - "block2", + "block2 0.5.1", "objc2", "objc2-contacts", "objc2-foundation", @@ -3374,7 +3403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.6.0", - "block2", + "block2 0.5.1", "dispatch", "libc", "objc2", @@ -3386,7 +3415,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ - "block2", + "block2 0.5.1", "objc2", "objc2-app-kit", "objc2-foundation", @@ -3399,11 +3428,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.6.0", - "block2", + "block2 0.5.1", "objc2", "objc2-foundation", ] +[[package]] +name = "objc2-osa-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6788b04a18ea31e3dc3ab256b8546639e5bbae07c1a0dc4ea8615252bc6aee9a" +dependencies = [ + "bitflags 2.6.0", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -3411,7 +3452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.6.0", - "block2", + "block2 0.5.1", "objc2", "objc2-foundation", "objc2-metal", @@ -3434,7 +3475,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.6.0", - "block2", + "block2 0.5.1", "objc2", "objc2-cloud-kit", "objc2-core-data", @@ -3454,7 +3495,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ - "block2", + "block2 0.5.1", "objc2", "objc2-foundation", ] @@ -3466,7 +3507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.6.0", - "block2", + "block2 0.5.1", "objc2", "objc2-core-location", "objc2-foundation", @@ -3479,7 +3520,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65" dependencies = [ "bitflags 2.6.0", - "block2", + "block2 0.5.1", "objc2", "objc2-app-kit", "objc2-foundation", @@ -3611,6 +3652,20 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "osakit" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35366a452fce3f8947eb2f33226a133aaf0cacedef2af67ade348d58be7f85d0" +dependencies = [ + "icrate", + "objc2-foundation", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 1.0.63", +] + [[package]] name = "pad" version = "0.1.6" @@ -4513,7 +4568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8af382a047821a08aa6bfc09ab0d80ff48d45d8726f7cd8e44891f7cb4a4278e" dependencies = [ "ashpd", - "block2", + "block2 0.5.1", "glib-sys", "gobject-sys", "gtk-sys", @@ -5980,17 +6035,18 @@ dependencies = [ [[package]] name = "tauri-plugin-updater" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad3de2b9203bb00b9765e637a9878aaace34df40ae484878b8cea7a5bd5f9188" +checksum = "ebf3da08c36fb03c98c76e5563d4e74d9a590df0f40978cbe07f39cb52833f7c" dependencies = [ "base64 0.22.1", - "dirs 5.0.1", + "dirs 6.0.0", "flate2", "futures-util", "http", "infer", "minisign-verify", + "osakit", "percent-encoding", "reqwest", "semver", @@ -7544,7 +7600,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e644bf458e27b11b0ecafc9e5633d1304fdae82baca1d42185669752fe6ca4f" dependencies = [ "base64 0.22.1", - "block2", + "block2 0.5.1", "cookie", "crossbeam-channel", "dpi", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 08d864f5..5b9c4bc6 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -67,7 +67,7 @@ tauri-plugin-opener = "2.2.5" tauri-plugin-os = "2.2.0" tauri-plugin-shell = { workspace = true } tauri-plugin-single-instance = "2.2.1" -tauri-plugin-updater = "2.4.0" +tauri-plugin-updater = "2.5.0" tauri-plugin-window-state = "2.2.1" tokio = { version = "1.43.0", features = ["sync"] } tokio-stream = "0.1.17" diff --git a/src-web/components/GitDropdown.tsx b/src-web/components/GitDropdown.tsx index 059ed2da..83d6f198 100644 --- a/src-web/components/GitDropdown.tsx +++ b/src-web/components/GitDropdown.tsx @@ -336,12 +336,14 @@ function SetupSyncDropdown({ workspaceMeta }: { workspaceMeta: WorkspaceMeta }) label: banner, }, { + color: 'success', label: 'Open Workspace Settings', leftSlot: , onSelect() { openWorkspaceSettings.mutate({ openSyncMenu: true }); }, }, + { type: 'separator' }, { label: 'Hide This Message', leftSlot: , @@ -396,8 +398,8 @@ function SetupGitDropdown({ leftSlot: , onSelect: initRepo, }, + { type: 'separator' }, { - color: 'warning', label: 'Hide This Message', leftSlot: , async onSelect() { diff --git a/src-web/components/LicenseBadge.tsx b/src-web/components/LicenseBadge.tsx index 06dfedc6..45ca9057 100644 --- a/src-web/components/LicenseBadge.tsx +++ b/src-web/components/LicenseBadge.tsx @@ -1,14 +1,14 @@ -import { openUrl } from '@tauri-apps/plugin-opener'; import type { LicenseCheckStatus } from '@yaakapp-internal/license'; import { useLicense } from '@yaakapp-internal/license'; import type { ReactNode } from 'react'; +import { openSettings } from '../commands/openSettings'; import { appInfo } from '../hooks/useAppInfo'; +import { useLicenseConfirmation } from '../hooks/useLicenseConfirmation'; import type { ButtonProps } from './core/Button'; import { Button } from './core/Button'; import { Icon } from './core/Icon'; import { HStack } from './core/Stacks'; -import { openSettings } from '../commands/openSettings'; -import {SettingsTab} from "./Settings/SettingsTab"; +import { SettingsTab } from './Settings/SettingsTab'; const details: Record< LicenseCheckStatus['type'] | 'dev' | 'beta', @@ -26,22 +26,30 @@ const details: Record< dev: { label: 'Develop', color: 'secondary' }, commercial_use: null, invalid_license: { label: 'License Error', color: 'danger' }, - personal_use: { label: 'Personal Use', color: 'primary' }, - trialing: { label: 'Personal Use', color: 'primary' }, + personal_use: { label: 'Personal Use', color: 'success' }, + trialing: { label: 'Active Trial', color: 'success' }, }; export function LicenseBadge() { const { check } = useLicense(); + const [licenseDetails, setLicenseDetails] = useLicenseConfirmation(); - if (check.data == null) { + // Hasn't loaded yet + if (licenseDetails == null || check.data == null) { return null; } - const checkType = appInfo.version.includes('beta') - ? 'beta' - : appInfo.isDev - ? 'dev' - : check.data.type; + // User has confirmed they are using Yaak for personal use only, so hide badge + if (licenseDetails.confirmedPersonalUse) { + return null; + } + + // User is trialing but has already seen the message, so hide badge + if (check.data.type === 'trialing' && licenseDetails.hasDismissedTrial) { + return null; + } + + const checkType = appInfo.version.includes('beta') ? 'beta' : check.data.type; const detail = details[checkType]; if (detail == null) { return null; @@ -53,11 +61,13 @@ export function LicenseBadge() { variant="border" className="!rounded-full mx-1" onClick={async () => { - if (checkType === 'beta') { - await openUrl('https://feedback.yaak.app'); - } else { - openSettings.mutate(SettingsTab.License); + if (check.data.type === 'trialing') { + await setLicenseDetails((v) => ({ + ...v, + dismissedTrial: true, + })); } + openSettings.mutate(SettingsTab.License); }} color={detail.color} event={{ id: 'license-badge', status: check.data.type }} diff --git a/src-web/components/Settings/SettingsLicense.tsx b/src-web/components/Settings/SettingsLicense.tsx index 7d57d40b..9cb58c90 100644 --- a/src-web/components/Settings/SettingsLicense.tsx +++ b/src-web/components/Settings/SettingsLicense.tsx @@ -1,47 +1,75 @@ +import { openUrl } from '@tauri-apps/plugin-opener'; import { useLicense } from '@yaakapp-internal/license'; import { formatDistanceToNowStrict } from 'date-fns'; import React, { useState } from 'react'; +import { useLicenseConfirmation } from '../../hooks/useLicenseConfirmation'; import { useToggle } from '../../hooks/useToggle'; import { Banner } from '../core/Banner'; import { Button } from '../core/Button'; +import { Checkbox } from '../core/Checkbox'; import { Icon } from '../core/Icon'; import { Link } from '../core/Link'; import { PlainInput } from '../core/PlainInput'; import { HStack, VStack } from '../core/Stacks'; -import { openUrl } from '@tauri-apps/plugin-opener'; export function SettingsLicense() { const { check, activate } = useLicense(); const [key, setKey] = useState(''); const [activateFormVisible, toggleActivateFormVisible] = useToggle(false); + const [licenseDetails, setLicenseDetails] = useLicenseConfirmation(); + const [checked, setChecked] = useState(false); if (check.isPending) { return null; } return ( -
+
{check.data?.type === 'commercial_use' ? ( License active! Enjoy using Yaak for commercial use. - ) : ( - - {check.data?.type === 'trialing' && ( -

- - You have {formatDistanceToNowStrict(check.data.end)} remaining on your trial. - -

- )} + ) : check.data?.type == 'trialing' ? ( +

- A commercial license is required if using Yaak within a for-profit organization.{' '} - - Learn More - + {formatDistanceToNowStrict(check.data.end)} days remaining on your + commercial use trial

- )} + ) : check.data?.type == 'personal_use' && !licenseDetails?.confirmedPersonalUse ? ( + +

+ Your 30-day trial has ended. Please activate a license or confirm how you're using + Yaak. +

+
{ + e.preventDefault(); + await setLicenseDetails((v) => ({ + ...v, + confirmedPersonalUse: true, + })); + }} + > + + + +
+ ) : null} + +

+ A commercial license is required if using Yaak within a for-profit organization.{' '} + + Learn More + +

{check.error && {check.error}} {activate.error && {activate.error}} @@ -80,7 +108,7 @@ export function SettingsLicense() {