mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-22 08:38:29 +02:00
Rework licensing flows to be more friendly
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
"core:event:allow-emit",
|
"core:event:allow-emit",
|
||||||
"core:event:allow-listen",
|
"core:event:allow-listen",
|
||||||
"core:event:allow-unlisten",
|
"core:event:allow-unlisten",
|
||||||
|
"core:path:allow-resolve-directory",
|
||||||
"os:allow-os-type",
|
"os:allow-os-type",
|
||||||
"clipboard-manager:allow-clear",
|
"clipboard-manager:allow-clear",
|
||||||
"clipboard-manager:allow-write-text",
|
"clipboard-manager:allow-write-text",
|
||||||
|
|||||||
BIN
src-tauri/static/greg.jpeg
Normal file
BIN
src-tauri/static/greg.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -15,7 +15,8 @@
|
|||||||
"enable": true,
|
"enable": true,
|
||||||
"scope": {
|
"scope": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"$APPDATA/responses/*"
|
"$APPDATA/responses/*",
|
||||||
|
"$RESOURCE/static/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,6 +57,7 @@
|
|||||||
],
|
],
|
||||||
"longDescription": "A cross-platform desktop app for interacting with REST, GraphQL, and gRPC",
|
"longDescription": "A cross-platform desktop app for interacting with REST, GraphQL, and gRPC",
|
||||||
"resources": [
|
"resources": [
|
||||||
|
"static",
|
||||||
"vendored/protoc/include",
|
"vendored/protoc/include",
|
||||||
"vendored/plugins",
|
"vendored/plugins",
|
||||||
"vendored/plugin-runtime"
|
"vendored/plugin-runtime"
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ pub async fn check_license<R: Runtime>(window: &WebviewWindow<R>) -> Result<Lice
|
|||||||
app_version: window.package_info().version.to_string(),
|
app_version: window.package_info().version.to_string(),
|
||||||
};
|
};
|
||||||
let activation_id = get_activation_id(window.app_handle()).await;
|
let activation_id = get_activation_id(window.app_handle()).await;
|
||||||
|
|
||||||
let settings = window.db().get_settings();
|
let settings = window.db().get_settings();
|
||||||
let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS));
|
let trial_end = settings.created_at.add(Duration::from_secs(TRIAL_SECONDS));
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export type ProxySetting = { "type": "enabled", http: string, https: string, aut
|
|||||||
|
|
||||||
export type ProxySettingAuth = { user: string, password: string, };
|
export type ProxySettingAuth = { user: string, password: string, };
|
||||||
|
|
||||||
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, autoupdate: boolean, };
|
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, hideLicenseBadge: boolean, autoupdate: boolean, };
|
||||||
|
|
||||||
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };
|
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
ALTER TABLE settings
|
||||||
|
ADD COLUMN hide_license_badge BOOLEAN DEFAULT FALSE;
|
||||||
|
|
||||||
|
-- 2. Backfill based on old JSON
|
||||||
|
UPDATE settings
|
||||||
|
SET hide_license_badge = 1
|
||||||
|
WHERE EXISTS ( SELECT 1
|
||||||
|
FROM key_values kv
|
||||||
|
WHERE kv.key = 'license_confirmation'
|
||||||
|
AND JSON_EXTRACT(kv.value, '$.confirmedPersonalUse') = TRUE );
|
||||||
@@ -120,6 +120,7 @@ pub struct Settings {
|
|||||||
pub theme_dark: String,
|
pub theme_dark: String,
|
||||||
pub theme_light: String,
|
pub theme_light: String,
|
||||||
pub update_channel: String,
|
pub update_channel: String,
|
||||||
|
pub hide_license_badge: bool,
|
||||||
pub autoupdate: bool,
|
pub autoupdate: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,6 +170,7 @@ impl UpsertModelInfo for Settings {
|
|||||||
(ThemeDark, self.theme_dark.as_str().into()),
|
(ThemeDark, self.theme_dark.as_str().into()),
|
||||||
(ThemeLight, self.theme_light.as_str().into()),
|
(ThemeLight, self.theme_light.as_str().into()),
|
||||||
(UpdateChannel, self.update_channel.into()),
|
(UpdateChannel, self.update_channel.into()),
|
||||||
|
(HideLicenseBadge, self.hide_license_badge.into()),
|
||||||
(Autoupdate, self.autoupdate.into()),
|
(Autoupdate, self.autoupdate.into()),
|
||||||
(ColoredMethods, self.colored_methods.into()),
|
(ColoredMethods, self.colored_methods.into()),
|
||||||
(Proxy, proxy.into()),
|
(Proxy, proxy.into()),
|
||||||
@@ -192,6 +194,7 @@ impl UpsertModelInfo for Settings {
|
|||||||
SettingsIden::ThemeDark,
|
SettingsIden::ThemeDark,
|
||||||
SettingsIden::ThemeLight,
|
SettingsIden::ThemeLight,
|
||||||
SettingsIden::UpdateChannel,
|
SettingsIden::UpdateChannel,
|
||||||
|
SettingsIden::HideLicenseBadge,
|
||||||
SettingsIden::Autoupdate,
|
SettingsIden::Autoupdate,
|
||||||
SettingsIden::ColoredMethods,
|
SettingsIden::ColoredMethods,
|
||||||
]
|
]
|
||||||
@@ -223,6 +226,7 @@ impl UpsertModelInfo for Settings {
|
|||||||
hide_window_controls: row.get("hide_window_controls")?,
|
hide_window_controls: row.get("hide_window_controls")?,
|
||||||
update_channel: row.get("update_channel")?,
|
update_channel: row.get("update_channel")?,
|
||||||
autoupdate: row.get("autoupdate")?,
|
autoupdate: row.get("autoupdate")?,
|
||||||
|
hide_license_badge: row.get("hide_license_badge")?,
|
||||||
colored_methods: row.get("colored_methods")?,
|
colored_methods: row.get("colored_methods")?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ impl<'a> DbContext<'a> {
|
|||||||
update_channel: "stable".to_string(),
|
update_channel: "stable".to_string(),
|
||||||
autoupdate: true,
|
autoupdate: true,
|
||||||
colored_methods: false,
|
colored_methods: false,
|
||||||
|
hide_license_badge: false,
|
||||||
};
|
};
|
||||||
self.upsert(&settings, &UpdateSource::Background).expect("Failed to upsert settings")
|
self.upsert(&settings, &UpdateSource::Background).expect("Failed to upsert settings")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
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 { settingsAtom } from '@yaakapp-internal/models';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { openSettings } from '../commands/openSettings';
|
import { openSettings } from '../commands/openSettings';
|
||||||
import { useLicenseConfirmation } from '../hooks/useLicenseConfirmation';
|
|
||||||
import { appInfo } from '../lib/appInfo';
|
import { appInfo } from '../lib/appInfo';
|
||||||
import { BadgeButton } from './core/BadgeButton';
|
import { BadgeButton } from './core/BadgeButton';
|
||||||
import type { ButtonProps } from './core/Button';
|
import type { ButtonProps } from './core/Button';
|
||||||
@@ -19,7 +20,7 @@ const details: Record<
|
|||||||
|
|
||||||
export function LicenseBadge() {
|
export function LicenseBadge() {
|
||||||
const { check } = useLicense();
|
const { check } = useLicense();
|
||||||
const [licenseDetails, setLicenseDetails] = useLicenseConfirmation();
|
const settings = useAtomValue(settingsAtom);
|
||||||
|
|
||||||
if (appInfo.isDev) {
|
if (appInfo.isDev) {
|
||||||
return null;
|
return null;
|
||||||
@@ -32,17 +33,17 @@ export function LicenseBadge() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hasn't loaded yet
|
// Hasn't loaded yet
|
||||||
if (licenseDetails == null || check.data == null) {
|
if (check.data == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// User has confirmed they are using Yaak for personal use only, so hide badge
|
// Dismissed license badge
|
||||||
if (licenseDetails.confirmedPersonalUse) {
|
if (settings.hideLicenseBadge) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// User is trialing but has already seen the message, so hide badge
|
// User is trialing but has already seen the message, so hide badge
|
||||||
if (check.data.type === 'trialing' && licenseDetails.hasDismissedTrial) {
|
if (check.data.type === 'trialing') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,12 +56,6 @@ export function LicenseBadge() {
|
|||||||
<BadgeButton
|
<BadgeButton
|
||||||
color={detail.color}
|
color={detail.color}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (check.data.type === 'trialing') {
|
|
||||||
await setLicenseDetails((v) => ({
|
|
||||||
...v,
|
|
||||||
hasDismissedTrial: true,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
openSettings.mutate('license');
|
openSettings.mutate('license');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
33
src-web/components/LocalImage.tsx
Normal file
33
src-web/components/LocalImage.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||||
|
import { resolveResource } from '@tauri-apps/api/path';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
src: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LocalImage({ src: srcPath, className }: Props) {
|
||||||
|
const src = useQuery({
|
||||||
|
queryKey: ['local-image', srcPath],
|
||||||
|
queryFn: async () => {
|
||||||
|
const p = await resolveResource(srcPath);
|
||||||
|
console.log("LOADING SRC", srcPath, p)
|
||||||
|
return convertFileSrc(p);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src={src.data}
|
||||||
|
alt="Response preview"
|
||||||
|
className={classNames(
|
||||||
|
className,
|
||||||
|
'transition-opacity',
|
||||||
|
src.data == null ? 'opacity-0' : 'opacity-100',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -74,22 +74,22 @@ export default function Settings({ hide }: Props) {
|
|||||||
onChangeValue={setTab}
|
onChangeValue={setTab}
|
||||||
tabs={tabs.map((value) => ({ value, label: capitalize(value) }))}
|
tabs={tabs.map((value) => ({ value, label: capitalize(value) }))}
|
||||||
>
|
>
|
||||||
<TabContent value={TAB_GENERAL} className="overflow-y-auto h-full px-4">
|
<TabContent value={TAB_GENERAL} className="overflow-y-auto h-full p-8">
|
||||||
<SettingsGeneral />
|
<SettingsGeneral />
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value={TAB_INTERFACE} className="overflow-y-auto h-full px-4">
|
<TabContent value={TAB_INTERFACE} className="overflow-y-auto h-full p-8">
|
||||||
<SettingsInterface />
|
<SettingsInterface />
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value={TAB_THEME} className="overflow-y-auto h-full px-4">
|
<TabContent value={TAB_THEME} className="overflow-y-auto h-full p-8">
|
||||||
<SettingsTheme />
|
<SettingsTheme />
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value={TAB_PLUGINS} className="h-full px-4 grid grid-rows-1">
|
<TabContent value={TAB_PLUGINS} className="h-full grid grid-rows-1 p-8">
|
||||||
<SettingsPlugins />
|
<SettingsPlugins />
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value={TAB_PROXY} className="overflow-y-auto h-full px-4">
|
<TabContent value={TAB_PROXY} className="overflow-y-auto h-full p-8!">
|
||||||
<SettingsProxy />
|
<SettingsProxy />
|
||||||
</TabContent>
|
</TabContent>
|
||||||
<TabContent value={TAB_LICENSE} className="overflow-y-auto h-full px-4">
|
<TabContent value={TAB_LICENSE} className="overflow-y-auto h-full px-8 !py-4">
|
||||||
<SettingsLicense />
|
<SettingsLicense />
|
||||||
</TabContent>
|
</TabContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import { type } from '@tauri-apps/plugin-os';
|
import { type } from '@tauri-apps/plugin-os';
|
||||||
import { useFonts } from '@yaakapp-internal/fonts';
|
import { useFonts } from '@yaakapp-internal/fonts';
|
||||||
|
import { useLicense } from '@yaakapp-internal/license';
|
||||||
import type { EditorKeymap } from '@yaakapp-internal/models';
|
import type { EditorKeymap } from '@yaakapp-internal/models';
|
||||||
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||||
import { clamp } from '../../lib/clamp';
|
import { clamp } from '../../lib/clamp';
|
||||||
|
import { showConfirm } from '../../lib/confirm';
|
||||||
import { Checkbox } from '../core/Checkbox';
|
import { Checkbox } from '../core/Checkbox';
|
||||||
import { Icon } from '../core/Icon';
|
import { Icon } from '../core/Icon';
|
||||||
|
import { Link } from '../core/Link';
|
||||||
import { Select } from '../core/Select';
|
import { Select } from '../core/Select';
|
||||||
import { HStack, VStack } from '../core/Stacks';
|
import { HStack, VStack } from '../core/Stacks';
|
||||||
|
|
||||||
@@ -28,6 +31,7 @@ export function SettingsInterface() {
|
|||||||
const workspace = useAtomValue(activeWorkspaceAtom);
|
const workspace = useAtomValue(activeWorkspaceAtom);
|
||||||
const settings = useAtomValue(settingsAtom);
|
const settings = useAtomValue(settingsAtom);
|
||||||
const fonts = useFonts();
|
const fonts = useFonts();
|
||||||
|
const license = useLicense();
|
||||||
|
|
||||||
if (settings == null || workspace == null) {
|
if (settings == null || workspace == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -123,6 +127,31 @@ export function SettingsInterface() {
|
|||||||
title="Colorize Request Methods"
|
title="Colorize Request Methods"
|
||||||
onChange={(coloredMethods) => patchModel(settings, { coloredMethods })}
|
onChange={(coloredMethods) => patchModel(settings, { coloredMethods })}
|
||||||
/>
|
/>
|
||||||
|
{license.check.data?.type === 'personal_use' && (
|
||||||
|
<Checkbox
|
||||||
|
checked={!settings.licenseBadge}
|
||||||
|
title="Hide personal use badge"
|
||||||
|
onChange={async (hide) => {
|
||||||
|
if (hide) {
|
||||||
|
const confirmed = await showConfirm({
|
||||||
|
id: 'hide-license-badge',
|
||||||
|
title: 'Hide License Badge',
|
||||||
|
confirmText: 'Hide Badge',
|
||||||
|
description: (
|
||||||
|
<>
|
||||||
|
Only proceed if you’re using Yaak for personal projects only. If you’re using it
|
||||||
|
at work, please <Link href="https://yaak.app/">Purchase a License</Link>.
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
requireTyping: 'Personal Use',
|
||||||
|
color: 'notice',
|
||||||
|
});
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
await patchModel(settings, { licenseBadge: !hide });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{type() !== 'macos' && (
|
{type() !== 'macos' && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
@@ -2,88 +2,80 @@ import { openUrl } from '@tauri-apps/plugin-opener';
|
|||||||
import { useLicense } from '@yaakapp-internal/license';
|
import { useLicense } from '@yaakapp-internal/license';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useLicenseConfirmation } from '../../hooks/useLicenseConfirmation';
|
|
||||||
import { useToggle } from '../../hooks/useToggle';
|
import { useToggle } from '../../hooks/useToggle';
|
||||||
import { pluralizeCount } from '../../lib/pluralize';
|
import { pluralizeCount } from '../../lib/pluralize';
|
||||||
import { Banner } from '../core/Banner';
|
import { Banner } from '../core/Banner';
|
||||||
import { Button } from '../core/Button';
|
import { Button } from '../core/Button';
|
||||||
import { Checkbox } from '../core/Checkbox';
|
|
||||||
import { Icon } from '../core/Icon';
|
import { Icon } from '../core/Icon';
|
||||||
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';
|
||||||
|
import { LocalImage } from '../LocalImage';
|
||||||
|
|
||||||
export function SettingsLicense() {
|
export function SettingsLicense() {
|
||||||
const { check, activate, deactivate } = useLicense();
|
const { check, activate, deactivate } = useLicense();
|
||||||
const [key, setKey] = useState<string>('');
|
const [key, setKey] = useState<string>('');
|
||||||
const [activateFormVisible, toggleActivateFormVisible] = useToggle(false);
|
const [activateFormVisible, toggleActivateFormVisible] = useToggle(false);
|
||||||
const [licenseDetails, setLicenseDetails] = useLicenseConfirmation();
|
|
||||||
const [checked, setChecked] = useState<boolean>(false);
|
|
||||||
|
|
||||||
if (check.isPending) {
|
if (check.isPending) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6 max-w-lg">
|
<div className="flex flex-col gap-6 max-w-xl">
|
||||||
{check.data?.type === 'commercial_use' ? (
|
{check.data?.type === 'commercial_use' ? (
|
||||||
<Banner color="success">
|
<Banner color="success">Your license is active 🥳</Banner>
|
||||||
<strong>License active!</strong> Enjoy using Yaak for commercial use.
|
|
||||||
</Banner>
|
|
||||||
) : check.data?.type == 'trialing' ? (
|
) : check.data?.type == 'trialing' ? (
|
||||||
<Banner color="success" className="flex flex-col gap-3 max-w-lg">
|
<Banner color="success" className="flex flex-col gap-3 max-w-lg">
|
||||||
<p className="select-text">
|
<p className="select-text">
|
||||||
You have{' '}
|
|
||||||
<strong>
|
<strong>
|
||||||
{pluralizeCount('day', differenceInDays(check.data.end, new Date()))} remaining
|
{pluralizeCount('day', differenceInDays(check.data.end, new Date()))} remaining
|
||||||
</strong>{' '}
|
</strong>{' '}
|
||||||
on your commercial use trial. Once the trial ends you agree to only use Yaak for
|
on trial
|
||||||
personal use until a license is activated.
|
|
||||||
</p>
|
</p>
|
||||||
</Banner>
|
</Banner>
|
||||||
) : check.data?.type == 'personal_use' && !licenseDetails?.confirmedPersonalUse ? (
|
) : check.data?.type == 'personal_use' ? (
|
||||||
<Banner color="success" className="flex flex-col gap-3 max-w-lg">
|
<Banner color="success" className="flex flex-col gap-3 max-w-lg">
|
||||||
<p className="select-text">
|
<p>Your free trial has ended</p>
|
||||||
Your 30-day trial has ended. Please activate a license or confirm how you're using
|
|
||||||
Yaak.
|
|
||||||
</p>
|
|
||||||
<form
|
|
||||||
className="flex flex-col gap-3 items-start"
|
|
||||||
onSubmit={async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
await setLicenseDetails((v) => ({
|
|
||||||
...v,
|
|
||||||
confirmedPersonalUse: true,
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
checked={checked}
|
|
||||||
onChange={setChecked}
|
|
||||||
title="I am only using Yaak for personal use"
|
|
||||||
/>
|
|
||||||
<Button type="submit" disabled={!checked} size="xs" variant="border" color="success">
|
|
||||||
Confirm
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</Banner>
|
</Banner>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<p className="select-text">
|
{check.data?.type !== 'commercial_use' && (
|
||||||
A commercial license is required if using Yaak within a for-profit organization.{' '}
|
<div className="grid grid-cols-[auto_minmax(0,1fr)] gap-6 items-center my-3 ">
|
||||||
<Link href="https://yaak.app/pricing" className="text-notice">
|
<LocalImage src="static/greg.jpeg" className="rounded-full h-20 w-20" />
|
||||||
Learn More
|
<div className="flex flex-col gap-2">
|
||||||
</Link>
|
<h2 className="text-lg font-bold">Hey, I'm Greg 👋🏼</h2>
|
||||||
</p>
|
<p>
|
||||||
|
Yaak is free for personal projects and learning.{' '}
|
||||||
|
{check.data?.type === 'trialing' ? 'After your trial, a ' : 'A '}
|
||||||
|
license is required for work or commercial use.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<Link
|
||||||
|
noUnderline
|
||||||
|
href="https://yaak.app/pricing"
|
||||||
|
className="text-sm text-notice opacity-80 hover:opacity-100"
|
||||||
|
>
|
||||||
|
Learn More
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{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>}
|
||||||
|
|
||||||
{check.data?.type === 'commercial_use' ? (
|
{check.data?.type === 'commercial_use' ? (
|
||||||
<HStack space={2}>
|
<HStack space={2}>
|
||||||
<Button variant="border" color="secondary" size="sm" onClick={() => {
|
<Button
|
||||||
deactivate.mutate();
|
variant="border"
|
||||||
}}>
|
color="secondary"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
deactivate.mutate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
Deactivate License
|
Deactivate License
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const icons = {
|
|||||||
code: lucide.CodeIcon,
|
code: lucide.CodeIcon,
|
||||||
columns_2: lucide.Columns2Icon,
|
columns_2: lucide.Columns2Icon,
|
||||||
command: lucide.CommandIcon,
|
command: lucide.CommandIcon,
|
||||||
|
credit_card: lucide.CreditCardIcon,
|
||||||
cookie: lucide.CookieIcon,
|
cookie: lucide.CookieIcon,
|
||||||
copy: lucide.CopyIcon,
|
copy: lucide.CopyIcon,
|
||||||
copy_check: lucide.CopyCheck,
|
copy_check: lucide.CopyCheck,
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { useKeyValue } from './useKeyValue';
|
|
||||||
|
|
||||||
interface LicenseConfirmation {
|
|
||||||
hasDismissedTrial: boolean;
|
|
||||||
confirmedPersonalUse: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useLicenseConfirmation() {
|
|
||||||
const { set, value } = useKeyValue<LicenseConfirmation>({
|
|
||||||
key: 'license_confirmation',
|
|
||||||
fallback: { hasDismissedTrial: false, confirmedPersonalUse: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
return [value, set] as const;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user