mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-22 08:38:29 +02:00
Tweak license flow
This commit is contained in:
@@ -81,18 +81,17 @@ pub async fn activate_license<R: Runtime>(
|
|||||||
if let Err(e) = window.emit("license-activated", true) {
|
if let Err(e) = window.emit("license-activated", true) {
|
||||||
warn!("Failed to emit check-license event: {}", e);
|
warn!("Failed to emit check-license event: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||||
#[serde(rename_all = "snake_case", tag = "type")]
|
#[serde(rename_all = "snake_case", tag = "type")]
|
||||||
#[ts(export, export_to = "license.ts")]
|
#[ts(export, export_to = "license.ts")]
|
||||||
pub enum LicenseCheckStatus {
|
pub enum LicenseCheckStatus {
|
||||||
PersonalUse,
|
PersonalUse { trial_ended: NaiveDateTime },
|
||||||
CommercialUse,
|
CommercialUse,
|
||||||
InvalidLicense,
|
InvalidLicense,
|
||||||
Trialing { end: NaiveDateTime },
|
Trialing { end: NaiveDateTime },
|
||||||
TrialEnded { end: NaiveDateTime },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn check_license<R: Runtime>(app_handle: &AppHandle<R>) -> Result<LicenseCheckStatus> {
|
pub async fn check_license<R: Runtime>(app_handle: &AppHandle<R>) -> Result<LicenseCheckStatus> {
|
||||||
@@ -114,7 +113,7 @@ pub async fn check_license<R: Runtime>(app_handle: &AppHandle<R>) -> Result<Lice
|
|||||||
|
|
||||||
match (has_activation_id, trial_period_active) {
|
match (has_activation_id, trial_period_active) {
|
||||||
(false, true) => Ok(LicenseCheckStatus::Trialing { end: trial_end }),
|
(false, true) => Ok(LicenseCheckStatus::Trialing { end: trial_end }),
|
||||||
(false, false) => Ok(LicenseCheckStatus::TrialEnded { end: trial_end }),
|
(false, false) => Ok(LicenseCheckStatus::PersonalUse { trial_ended: trial_end }),
|
||||||
(true, _) => {
|
(true, _) => {
|
||||||
info!("Checking license activation");
|
info!("Checking license activation");
|
||||||
// A license has been activated, so let's check the license server
|
// A license has been activated, so let's check the license server
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import type { LicenseCheckStatus } from '@yaakapp-internal/license';
|
import type { LicenseCheckStatus } from '@yaakapp-internal/license';
|
||||||
import { useLicense } from '@yaakapp-internal/license';
|
import { useLicense } from '@yaakapp-internal/license';
|
||||||
import { useOpenSettings } from '../hooks/useOpenSettings';
|
import { useOpenSettings } from '../hooks/useOpenSettings';
|
||||||
|
import type { ButtonProps } from './core/Button';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { SettingsTab } from './Settings/Settings';
|
import { SettingsTab } from './Settings/Settings';
|
||||||
|
|
||||||
const labels: Record<LicenseCheckStatus['type'], string | null> = {
|
const details: Record<
|
||||||
|
LicenseCheckStatus['type'],
|
||||||
|
{ label: string; color: ButtonProps['color'] } | null
|
||||||
|
> = {
|
||||||
commercial_use: null,
|
commercial_use: null,
|
||||||
personal_use: 'Personal Use',
|
invalid_license: { label: 'Invalid License', color: 'danger' },
|
||||||
trial_ended: 'Personal Use',
|
personal_use: { label: 'Personal Use', color: 'success' },
|
||||||
trialing: 'Active Trial',
|
trialing: { label: 'Personal Use', color: 'success' },
|
||||||
};
|
};
|
||||||
|
|
||||||
export function LicenseBadge() {
|
export function LicenseBadge() {
|
||||||
@@ -19,8 +23,8 @@ export function LicenseBadge() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const label = labels[check.data.type];
|
const detail = details[check.data.type];
|
||||||
if (label == null) {
|
if (detail == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,13 +34,9 @@ export function LicenseBadge() {
|
|||||||
variant="border"
|
variant="border"
|
||||||
className="!rounded-full mx-1"
|
className="!rounded-full mx-1"
|
||||||
onClick={() => openSettings.mutate()}
|
onClick={() => openSettings.mutate()}
|
||||||
color={
|
color={detail.color}
|
||||||
check.data.type == 'trial_ended' || check.data.type === 'personal_use'
|
|
||||||
? 'primary'
|
|
||||||
: 'success'
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{label}
|
{detail.label}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { open } from '@tauri-apps/plugin-shell';
|
import { open } from '@tauri-apps/plugin-shell';
|
||||||
import { useLicense } from '@yaakapp-internal/license';
|
import { useLicense } from '@yaakapp-internal/license';
|
||||||
import classNames from 'classnames';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import { format, formatDistanceToNow } from 'date-fns';
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useCopy } from '../../hooks/useCopy';
|
|
||||||
import { useSettings } from '../../hooks/useSettings';
|
|
||||||
import { useTimedBoolean } from '../../hooks/useTimedBoolean';
|
|
||||||
import { useToggle } from '../../hooks/useToggle';
|
import { useToggle } from '../../hooks/useToggle';
|
||||||
import { Banner } from '../core/Banner';
|
import { Banner } from '../core/Banner';
|
||||||
import { Button } from '../core/Button';
|
import { Button } from '../core/Button';
|
||||||
import { Icon } from '../core/Icon';
|
import { Icon } from '../core/Icon';
|
||||||
import { InlineCode } from '../core/InlineCode';
|
|
||||||
import { Link } from '../core/Link';
|
import { Link } from '../core/Link';
|
||||||
import { PlainInput } from '../core/PlainInput';
|
import { PlainInput } from '../core/PlainInput';
|
||||||
import { HStack, VStack } from '../core/Stacks';
|
import { HStack, VStack } from '../core/Stacks';
|
||||||
@@ -19,15 +14,13 @@ export function SettingsLicense() {
|
|||||||
const { check, activate } = useLicense();
|
const { check, activate } = useLicense();
|
||||||
const [key, setKey] = useState<string>('');
|
const [key, setKey] = useState<string>('');
|
||||||
const [activateFormVisible, toggleActivateFormVisible] = useToggle(false);
|
const [activateFormVisible, toggleActivateFormVisible] = useToggle(false);
|
||||||
const settings = useSettings();
|
|
||||||
const specialAnnouncement =
|
if (check.isPending) {
|
||||||
settings.createdAt < '2024-12-03' && check.data?.type !== 'commercial_use';
|
return null;
|
||||||
const [copied, setCopied] = useTimedBoolean();
|
}
|
||||||
const copy = useCopy({ disableToast: true });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
{check.data?.type === 'personal_use' && <Banner color="info">You're</Banner>}
|
|
||||||
{check.data?.type === 'commercial_use' && (
|
{check.data?.type === 'commercial_use' && (
|
||||||
<Banner color="success">
|
<Banner color="success">
|
||||||
<strong>License active!</strong> Enjoy using Yaak for commercial use.
|
<strong>License active!</strong> Enjoy using Yaak for commercial use.
|
||||||
@@ -39,56 +32,24 @@ export function SettingsLicense() {
|
|||||||
using Yaak for commercial use, please purchase a commercial use license.
|
using Yaak for commercial use, please purchase a commercial use license.
|
||||||
</Banner>
|
</Banner>
|
||||||
)}
|
)}
|
||||||
{check.data?.type === 'trial_ended' && !specialAnnouncement && (
|
{check.data?.type === 'personal_use' && (
|
||||||
<Banner color="primary">
|
<Banner color="primary" className="flex flex-col gap-2">
|
||||||
<strong>Your trial ended on {format(check.data.end, 'MMMM dd, yyyy')}</strong>. A
|
<h2 className="text-lg font-semibold">Commercial License</h2>
|
||||||
commercial-use license is required if you use Yaak within a for-profit organization of two
|
<p>
|
||||||
or more people.
|
A commercial license is required if you use Yaak within a for-profit organization of two
|
||||||
|
or more people.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Link href="https://yaak.app/pricing" className="text-sm">
|
||||||
|
Learn More
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
</Banner>
|
</Banner>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{check.error && <Banner color="danger">{check.error}</Banner>}
|
{check.error && <Banner color="danger">{check.error}</Banner>}
|
||||||
{activate.error && <Banner color="danger">{activate.error}</Banner>}
|
{activate.error && <Banner color="danger">{activate.error}</Banner>}
|
||||||
|
|
||||||
{specialAnnouncement && (
|
|
||||||
<VStack className="max-w-lg" space={4}>
|
|
||||||
<p>
|
|
||||||
<strong>Thank you for being an early supporter of Yaak!</strong>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
To support the ongoing development of the best local-first API client, Yaak now requires
|
|
||||||
a paid license for the commercial use of prebuilt binaries (personal use and running the
|
|
||||||
open-source code remains free.)
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
For details, see the{' '}
|
|
||||||
<Link href="https://yaak.app/blog/commercial-use">Announcement Post</Link>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
As a thank-you, enter code{' '}
|
|
||||||
<button
|
|
||||||
title="Copy coupon code"
|
|
||||||
className="hover:text-notice"
|
|
||||||
onClick={() => {
|
|
||||||
setCopied();
|
|
||||||
copy('EARLYAAK');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<InlineCode className="inline-flex items-center gap-1">
|
|
||||||
EARLYAAK{' '}
|
|
||||||
<Icon
|
|
||||||
icon={copied ? 'check' : 'copy'}
|
|
||||||
size="xs"
|
|
||||||
className={classNames(copied && 'text-success')}
|
|
||||||
/>
|
|
||||||
</InlineCode>
|
|
||||||
</button>{' '}
|
|
||||||
at checkout for 50% off your first year of the individual plan.
|
|
||||||
</p>
|
|
||||||
<p>~ Greg</p>
|
|
||||||
</VStack>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{check.data?.type === 'commercial_use' ? (
|
{check.data?.type === 'commercial_use' ? (
|
||||||
<HStack space={2}>
|
<HStack space={2}>
|
||||||
<Button variant="border" color="secondary" size="sm" onClick={toggleActivateFormVisible}>
|
<Button variant="border" color="secondary" size="sm" onClick={toggleActivateFormVisible}>
|
||||||
@@ -105,6 +66,9 @@ export function SettingsLicense() {
|
|||||||
</HStack>
|
</HStack>
|
||||||
) : (
|
) : (
|
||||||
<HStack space={2}>
|
<HStack space={2}>
|
||||||
|
<Button color="primary" size="sm" onClick={toggleActivateFormVisible}>
|
||||||
|
Activate License
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -113,9 +77,6 @@ export function SettingsLicense() {
|
|||||||
>
|
>
|
||||||
Purchase
|
Purchase
|
||||||
</Button>
|
</Button>
|
||||||
<Button color="primary" size="sm" onClick={toggleActivateFormVisible}>
|
|
||||||
Activate License
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export function Banner({ children, className, color = 'secondary' }: Props) {
|
|||||||
className,
|
className,
|
||||||
`x-theme-banner--${color}`,
|
`x-theme-banner--${color}`,
|
||||||
'whitespace-pre-wrap',
|
'whitespace-pre-wrap',
|
||||||
'border border-dashed border-border-subtle bg-surface',
|
'border border-dashed border-border bg-surface',
|
||||||
'italic px-3 py-2 rounded select-auto cursor-text',
|
'px-3 py-2 rounded select-auto cursor-text',
|
||||||
'overflow-x-auto text-text',
|
'overflow-x-auto text-text',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user