feat: UI polish sprint — 7 items + logs toolbar redesign (#365)

* feat: UI polish sprint — tag filters, toast tokens, logs toolbar, billing portal, editor UX

- Fleet: replace inline tag pills with multi-select combobox dropdown
- Toast: remap all notification colors to oklch design tokens
- Logs: convert floating hover toolbar to permanent pinned toolbar,
  replace all hardcoded colors with design tokens for theme support
- Audit: fix dropdown scroll-lock (modal=false), light theme button contrast
- Resources: remove redundant inner border/bg on tab wrapper
- Editor: add ⌘K/Ctrl+K shortcut hint and handler, standardize button heights
- Billing: signed Lemon Squeezy portal URLs via sencho.io proxy for all tiers
- New MultiSelectCombobox UI component

* feat(editor): replace button row with split-button dropdown

Replace three separate editor buttons (Discard, Save Only, Save & Deploy)
with a compact split-button dropdown. Primary action is "Save & Deploy";
chevron opens dropdown with "Save Only" and "Discard Changes" options.
This commit is contained in:
Anso
2026-04-03 20:33:44 -04:00
committed by GitHub
parent 2a277eb09d
commit f9ebd1d77c
13 changed files with 417 additions and 105 deletions
@@ -8,14 +8,33 @@ import { useLicense } from '@/context/LicenseContext';
import { TierBadge } from '@/components/TierBadge';
import {
Crown, CheckCircle, Check, XCircle, Clock, ExternalLink,
CreditCard, RefreshCw, Zap, Compass, ShipWheel,
CreditCard, RefreshCw, Zap, Compass, ShipWheel, Loader2,
} from 'lucide-react';
import { apiFetch } from '@/lib/api';
export function LicenseSection() {
const { license, activate, deactivate } = useLicense();
const [licenseKeyInput, setLicenseKeyInput] = useState('');
const [isActivating, setIsActivating] = useState(false);
const [isDeactivating, setIsDeactivating] = useState(false);
const [billingLoading, setBillingLoading] = useState(false);
const openBillingPortal = async () => {
setBillingLoading(true);
try {
const res = await apiFetch('/license/billing-portal', { localOnly: true });
const data = await res.json().catch(() => ({}));
if (res.ok && data.url) {
window.open(data.url, '_blank');
return;
}
toast.error(data?.error || data?.message || data?.data?.error || 'Something went wrong.');
} catch {
toast.error('Failed to open billing portal.');
} finally {
setBillingLoading(false);
}
};
return (
<div className="space-y-6">
@@ -94,17 +113,20 @@ export function LicenseSection() {
{/* Manage Subscription (active Pro) */}
{license?.status === 'active' && (
<div className="space-y-3">
{license.portalUrl && (
<Button
variant="outline"
size="sm"
onClick={() => window.open(license.portalUrl!, '_blank')}
>
<Button
variant="outline"
size="sm"
onClick={openBillingPortal}
disabled={billingLoading}
>
{billingLoading ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
) : (
<CreditCard className="w-4 h-4 mr-2" />
Manage Subscription
<ExternalLink className="w-3 h-3 ml-1.5 opacity-50" />
</Button>
)}
)}
Manage Subscription
<ExternalLink className="w-3 h-3 ml-1.5 opacity-50" />
</Button>
<div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground">
Deactivating will revert to Community features.