/** * Velxio Desktop lockout overlay (v0.3.0+). * * Full-screen modal mounted by `./index.ts` when the Tauri shell emits * `velxio://license-required`. Cannot be dismissed — only resolved by * either: * * 1. Signing in via Velxio (deep-link OAuth) → shell saves the key, * we call `restartApp()` so the sidecar re-evaluates the gate. * 2. Pasting a license key directly. * 3. Closing the app (X in the title bar — Tauri handles that). * * Three visual variants driven by `reason`: * * - `'no_credential'` (default) — fresh install with no key and the * grandfather window already exhausted. Friendly tone: "trial * available, sign up to continue". * - `'tampered'` — JWT verification failed. Implies user tampered * with the keychain entry OR our verification key rotated and * the cached token is on an old kid. Slightly less friendly: * "authentication problem, please sign in again". Offers a * "Clear stored credentials" button as a self-service repair. * - `'expired'` — had a key, ran out of soft + hard grace. Tone: * "subscription expired, reactivate to continue". * * NEVER tries to call the sidecar (it isn't running — that's the * whole point). All actions go through `license_*` Tauri commands * which are shell-side (keychain + network to velxio.dev). */ import { useEffect, useState } from 'react'; import { beginSignIn, invoke, isTauri, listen, openExternal, restartApp, type ValidationResult, } from './tauriBridge'; export type LockoutReason = 'no_credential' | 'tampered' | 'expired'; type Props = { reason: LockoutReason; }; const VELXIO_BASE = 'https://velxio.dev'; const COPY: Record = { no_credential: { title: 'Sign in to continue', subtitle: 'Velxio Desktop requires an account. Start your 30-day free trial - no credit card needed.', primary: 'Start free trial', }, tampered: { title: 'Authentication problem', subtitle: 'Your stored credentials could not be verified. Please sign in again to continue.', primary: 'Sign in again', }, expired: { title: 'Subscription expired', subtitle: 'Reactivate your Velxio Pro subscription to keep using Velxio Desktop.', primary: 'Reactivate subscription', }, }; type Mode = 'choose' | 'paste'; export const LockoutOverlay = ({ reason }: Props) => { const copy = COPY[reason]; const [mode, setMode] = useState('choose'); const [pastedKey, setPastedKey] = useState(''); const [busy, setBusy] = useState(false); const [waiting, setWaiting] = useState(false); const [err, setErr] = useState(null); // Same deep-link listener as the welcome page — when the user // completes sign-in in the browser, the shell saves the key + emits // `velxio://auth-completed`. Restart so the sidecar starts. useEffect(() => { let dispose: (() => void) | null = null; listen<{ ok: boolean; result?: ValidationResult; error?: string }>( 'velxio://auth-completed', (event) => { setWaiting(false); if (event.payload.ok && event.payload.result?.valid) { // Don't even try to mount the editor — sidecar is dead. // Restart the shell so setup() re-runs and spawns it. void restartApp(); } else { setErr(event.payload.error || 'Sign-in failed. Try again.'); } }, ).then((off) => { dispose = off; }); return () => { if (dispose) dispose(); }; }, []); const handleSignIn = async () => { setErr(null); if (!isTauri()) { setErr('This window is running outside Tauri - paste your key instead.'); setMode('paste'); return; } setBusy(true); try { await beginSignIn(VELXIO_BASE); setWaiting(true); } catch (e) { setErr(e instanceof Error ? e.message : String(e)); } finally { setBusy(false); } }; const handleClear = async () => { setBusy(true); setErr(null); try { await invoke('license_clear_key'); // After clearing, restart so the gate sees the empty state and // routes to the no_credential variant instead of tampered. await restartApp(); } catch (e) { setErr(e instanceof Error ? e.message : String(e)); setBusy(false); } }; const handlePaste = async () => { setErr(null); const trimmed = pastedKey.trim(); if (!trimmed) { setErr('Paste a license key first.'); return; } if (!/^vlx_[a-z_]+_[0-9a-f]+$/.test(trimmed)) { setErr("That doesn't look like a Velxio key (vlx__)."); return; } setBusy(true); try { await invoke('license_save_key', { key: trimmed }); const result = await invoke('license_validate', { key: trimmed }); if (result.valid && result.entitlements?.desktop) { // Same as the deep-link path: restart so the sidecar boots. await restartApp(); } else { setErr( result.reason_code === 'trial_expired' ? 'Your trial has ended. Upgrade to Pro to continue.' : 'Key was accepted but the Velxio Pro entitlement is not active.', ); } } catch (e) { setErr(e instanceof Error ? e.message : String(e)); } finally { setBusy(false); } }; return (

{copy.title}

{copy.subtitle}

{err &&
{err}
} {mode === 'choose' && (
{reason === 'tampered' && ( )}
)} {mode === 'paste' && (
setPastedKey(e.target.value)} placeholder="vlx_pro_... or vlx_trial_..." spellCheck={false} autoFocus disabled={busy} />
)}
); };