Better trial activation flows

This commit is contained in:
Gregory Schier
2025-02-25 22:16:55 -08:00
parent 80de232bec
commit eb8153f409
6 changed files with 26 additions and 22 deletions

View File

@@ -26,6 +26,7 @@ export function useLicense() {
const CHECK_QUERY_KEY = ['license.check']; const CHECK_QUERY_KEY = ['license.check'];
const check = useQuery<void, string, LicenseCheckStatus>({ const check = useQuery<void, string, LicenseCheckStatus>({
refetchInterval: 1000 * 60 * 60 * 12, // Refetch every 12 hours
queryKey: CHECK_QUERY_KEY, queryKey: CHECK_QUERY_KEY,
queryFn: () => invoke('plugin:yaak-license|check'), queryFn: () => invoke('plugin:yaak-license|check'),
}); });

View File

@@ -65,7 +65,7 @@ export function LicenseBadge() {
if (check.data.type === 'trialing') { if (check.data.type === 'trialing') {
await setLicenseDetails((v) => ({ await setLicenseDetails((v) => ({
...v, ...v,
dismissedTrial: true, hasDismissedTrial: true,
})); }));
} }
openSettings.mutate(SettingsTab.License); openSettings.mutate(SettingsTab.License);

View File

@@ -1,9 +1,10 @@
import { openUrl } from '@tauri-apps/plugin-opener'; import { openUrl } from '@tauri-apps/plugin-opener';
import { useLicense } from '@yaakapp-internal/license'; import { useLicense } from '@yaakapp-internal/license';
import { formatDistanceToNowStrict } from 'date-fns'; import { differenceInDays } from 'date-fns';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useLicenseConfirmation } from '../../hooks/useLicenseConfirmation'; import { useLicenseConfirmation } from '../../hooks/useLicenseConfirmation';
import { useToggle } from '../../hooks/useToggle'; import { useToggle } from '../../hooks/useToggle';
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 { Checkbox } from '../core/Checkbox';
@@ -32,8 +33,12 @@ export function SettingsLicense() {
) : 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">
<strong>{formatDistanceToNowStrict(check.data.end)} days remaining</strong> on your You have{' '}
commercial use trial <strong>
{pluralizeCount('day', differenceInDays(check.data.end, new Date()))} remaining
</strong>{' '}
on your commercial use trial. Once the trial ends, Yaak will be limited to personal use
until a license is activated.
</p> </p>
</Banner> </Banner>
) : check.data?.type == 'personal_use' && !licenseDetails?.confirmedPersonalUse ? ( ) : check.data?.type == 'personal_use' && !licenseDetails?.confirmedPersonalUse ? (
@@ -76,12 +81,7 @@ export function SettingsLicense() {
{check.data?.type === 'commercial_use' ? ( {check.data?.type === 'commercial_use' ? (
<HStack space={2}> <HStack space={2}>
<Button <Button variant="border" color="secondary" size="sm" onClick={toggleActivateFormVisible}>
variant="border"
color="secondary"
size="sm"
onClick={toggleActivateFormVisible}
>
Activate Another License Activate Another License
</Button> </Button>
<Button <Button
@@ -95,11 +95,7 @@ export function SettingsLicense() {
</HStack> </HStack>
) : ( ) : (
<HStack space={2}> <HStack space={2}>
<Button <Button color="primary" size="sm" onClick={toggleActivateFormVisible}>
color="primary"
size="sm"
onClick={toggleActivateFormVisible}
>
Activate Activate
</Button> </Button>
<Button <Button
@@ -131,12 +127,7 @@ export function SettingsLicense() {
onChange={setKey} onChange={setKey}
placeholder="YK1-XXXXX-XXXXX-XXXXX-XXXXX" placeholder="YK1-XXXXX-XXXXX-XXXXX-XXXXX"
/> />
<Button <Button type="submit" color="primary" size="sm" isLoading={activate.isPending}>
type="submit"
color="primary"
size="sm"
isLoading={activate.isPending}
>
Submit Submit
</Button> </Button>
</VStack> </VStack>

View File

@@ -1,4 +1,5 @@
import { openUrl } from '@tauri-apps/plugin-opener'; import { openUrl } from '@tauri-apps/plugin-opener';
import { useLicense } from '@yaakapp-internal/license';
import { useRef } from 'react'; import { useRef } from 'react';
import { openSettings } from '../commands/openSettings'; import { openSettings } from '../commands/openSettings';
import { useAppInfo } from '../hooks/useAppInfo'; import { useAppInfo } from '../hooks/useAppInfo';
@@ -12,6 +13,7 @@ import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon'; import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton'; import { IconButton } from './core/IconButton';
import { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog'; import { KeyboardShortcutsDialog } from './KeyboardShortcutsDialog';
import { SettingsTab } from './Settings/SettingsTab';
export function SettingsDropdown() { export function SettingsDropdown() {
const importData = useImportData(); const importData = useImportData();
@@ -19,6 +21,7 @@ export function SettingsDropdown() {
const appInfo = useAppInfo(); const appInfo = useAppInfo();
const dropdownRef = useRef<DropdownRef>(null); const dropdownRef = useRef<DropdownRef>(null);
const checkForUpdates = useCheckForUpdates(); const checkForUpdates = useCheckForUpdates();
const { check } = useLicense();
useListenToTauriEvent('settings', () => openSettings.mutate(null)); useListenToTauriEvent('settings', () => openSettings.mutate(null));
@@ -56,6 +59,13 @@ export function SettingsDropdown() {
onSelect: () => exportData.mutate(), onSelect: () => exportData.mutate(),
}, },
{ type: 'separator', label: `Yaak v${appInfo.version}` }, { type: 'separator', label: `Yaak v${appInfo.version}` },
{
label: 'Purchase License',
color: 'success',
hidden: check.data == null || check.data.type === 'commercial_use',
leftSlot: <Icon icon="circle_dollar_sign" />,
onSelect: () => openSettings.mutate(SettingsTab.License),
},
{ {
label: 'Check for Updates', label: 'Check for Updates',
leftSlot: <Icon icon="update" />, leftSlot: <Icon icon="update" />,

View File

@@ -55,7 +55,7 @@ export type DropdownItemDefault = {
label: ReactNode; label: ReactNode;
hotKeyAction?: HotkeyAction; hotKeyAction?: HotkeyAction;
hotKeyLabelOnly?: boolean; hotKeyLabelOnly?: boolean;
color?: 'default' | 'danger' | 'info' | 'warning' | 'notice' | 'success'; color?: 'default' | 'primary' | 'danger' | 'info' | 'warning' | 'notice' | 'success';
disabled?: boolean; disabled?: boolean;
hidden?: boolean; hidden?: boolean;
leftSlot?: ReactNode; leftSlot?: ReactNode;
@@ -645,6 +645,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
'min-w-[8rem] outline-none px-2 mx-1.5 flex whitespace-nowrap', 'min-w-[8rem] outline-none px-2 mx-1.5 flex whitespace-nowrap',
'focus:bg-surface-highlight focus:text rounded', 'focus:bg-surface-highlight focus:text rounded',
item.color === 'danger' && '!text-danger', item.color === 'danger' && '!text-danger',
item.color === 'primary' && '!text-primary',
item.color === 'success' && '!text-success', item.color === 'success' && '!text-success',
item.color === 'warning' && '!text-warning', item.color === 'warning' && '!text-warning',
item.color === 'notice' && '!text-notice', item.color === 'notice' && '!text-notice',

View File

@@ -8,6 +8,7 @@ const icons = {
alert_triangle: lucide.AlertTriangleIcon, alert_triangle: lucide.AlertTriangleIcon,
archive: lucide.ArchiveIcon, archive: lucide.ArchiveIcon,
arrow_big_down_dash: lucide.ArrowBigDownDashIcon, arrow_big_down_dash: lucide.ArrowBigDownDashIcon,
circle_dollar_sign: lucide.CircleDollarSignIcon,
arrow_right_circle: lucide.ArrowRightCircleIcon, arrow_right_circle: lucide.ArrowRightCircleIcon,
arrow_big_left_dash: lucide.ArrowBigLeftDashIcon, arrow_big_left_dash: lucide.ArrowBigLeftDashIcon,
arrow_big_right: lucide.ArrowBigRightIcon, arrow_big_right: lucide.ArrowBigRightIcon,