improve home screen

This commit is contained in:
2026-05-06 16:05:04 -06:00
parent 10437c02ca
commit e4484a57f9
9 changed files with 328 additions and 111 deletions

View File

@ -7,17 +7,43 @@ export default function LicenseDialog() {
const [key, setKey] = useState('');
const [error, setError] = useState<string | null>(null);
const [activating, setActivating] = useState(false);
const [confirmedEmail, setConfirmedEmail] = useState<string | null>(null);
const [verifying, setVerifying] = useState(false);
const handleActivate = async () => {
if (!key.trim()) return;
setActivating(true);
setError(null);
const ok = await activateLicense(key.trim());
if (!ok) {
// If we already verified and the user confirmed, complete activation
if (confirmedEmail) {
setActivating(true);
const ok = await activateLicense(key.trim());
if (!ok) {
setError('Invalid license key. Check it was entered correctly.');
}
setActivating(false);
return;
}
// Step 1: Verify the key (don't cache yet) to get the email
setVerifying(true);
try {
const payload = await window.electronAPI?.verifyLicense(key.trim());
if (payload?.customer_email) {
setConfirmedEmail(payload.customer_email);
} else {
setError('Invalid license key. Check it was entered correctly.');
}
} catch {
setError('Invalid license key. Check it was entered correctly.');
}
setActivating(false);
setVerifying(false);
};
const handleDeny = () => {
setConfirmedEmail(null);
setKey('');
setError(null);
};
const formatDate = (ts: number) => {
@ -58,41 +84,47 @@ export default function LicenseDialog() {
</button>
</div>
{showDialog && (
<LicenseActivateDialog
onClose={() => setShowDialog(false)}
onActivate={handleActivate}
keyValue={key}
setKeyValue={setKey}
error={error}
activating={activating}
trialEnding={status.days_remaining <= 3}
/>
)}
</>
);
}
// Expired — show banner + activation dialog (both dismissible)
return (
<>
<ExpiredBanner onActivate={() => setShowDialog(true)} />
{showDialog && (
<LicenseActivateDialog
onClose={() => setShowDialog(false)}
onClose={() => { setShowDialog(false); handleDeny(); }}
onActivate={handleActivate}
onDeny={handleDeny}
keyValue={key}
setKeyValue={setKey}
error={error}
activating={activating}
expired
verifying={verifying}
confirmedEmail={confirmedEmail}
trialEnding={status.days_remaining <= 3}
/>
)}
</>
);
}
// Expired — show banner + activation dialog (both dismissible)
return (
<>
<ExpiredBanner onActivate={() => setShowDialog(true)} />
{showDialog && (
<LicenseActivateDialog
onClose={() => { setShowDialog(false); handleDeny(); }}
onActivate={handleActivate}
onDeny={handleDeny}
keyValue={key}
setKeyValue={setKey}
error={error}
activating={activating}
verifying={verifying}
confirmedEmail={confirmedEmail}
expired
/>
)}
</>
);
}
/** Persistent top banner shown when trial expired — still allows export and loading */
function ExpiredBanner({ onActivate }: { onActivate: () => void }) {
return (
@ -112,22 +144,78 @@ function ExpiredBanner({ onActivate }: { onActivate: () => void }) {
function LicenseActivateDialog({
onClose,
onActivate,
onDeny,
keyValue,
setKeyValue,
error,
activating,
verifying,
confirmedEmail,
trialEnding,
expired,
}: {
onClose: () => void;
onActivate: () => void;
onDeny: () => void;
keyValue: string;
setKeyValue: (v: string) => void;
error: string | null;
activating: boolean;
verifying: boolean;
confirmedEmail: string | null;
trialEnding?: boolean;
expired?: boolean;
}) {
const isProcessing = activating || verifying;
if (confirmedEmail) {
return (
<div className="fixed inset-0 z-[80] flex items-center justify-center bg-black/60 px-4">
<div
className="w-full max-w-md rounded-xl border border-editor-border bg-editor-bg p-6 space-y-4"
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center gap-2">
<Shield className="w-5 h-5 text-editor-accent" />
<h3 className="text-sm font-semibold">Confirm License</h3>
</div>
<div className="p-3 rounded-lg bg-editor-surface border border-editor-border space-y-1">
<p className="text-xs text-editor-text-muted">
This license key is registered to:
</p>
<p className="text-sm font-medium text-editor-text">{confirmedEmail}</p>
</div>
<p className="text-xs text-editor-text-muted leading-relaxed">
License keys are tied to your email. Sharing this key may result in deactivation.
</p>
<div className="flex items-center justify-end gap-2 pt-1">
<button
onClick={onDeny}
className="px-3 py-1.5 rounded-md text-xs text-editor-text-muted hover:text-editor-text hover:bg-editor-surface"
>
Cancel
</button>
<button
onClick={onActivate}
disabled={activating}
className="px-4 py-2 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-40 rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
>
{activating ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Check className="w-4 h-4" />
)}
Activate
</button>
</div>
</div>
</div>
);
}
return (
<div className="fixed inset-0 z-[80] flex items-center justify-center bg-black/60 px-4">
<div
@ -188,15 +276,15 @@ function LicenseActivateDialog({
<button
onClick={onActivate}
disabled={activating || !keyValue.trim()}
disabled={isProcessing || !keyValue.trim()}
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-40 rounded-lg text-sm font-medium transition-colors"
>
{activating ? (
{isProcessing ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Check className="w-4 h-4" />
<Key className="w-4 h-4" />
)}
Activate
{verifying ? 'Verifying...' : 'Verify Key'}
</button>
<p className="text-[10px] text-editor-text-muted text-center">