/* @refresh reload */ import React from 'react'; import { createRoot } from 'react-dom/client'; import './styles.css'; import { Input } from './components/ui/input'; import { Label } from './components/ui/label'; import { SelectMenu } from './components/ui/select-menu'; import { Button } from './components/ui/button'; import { ColorPicker } from './components/ui/color-picker'; import { useToaster } from './components/ui/toast'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from './components/ui/card'; import { Badge } from './components/ui/badge'; type Settings = { activeProvider: string; pinnedVersion: string; palette: Array<{ token: string; label: string; hex: string }>; defaultToken: string; }; const MIN_LOADING_MS = 400; async function withMinDelay(promise: Promise, minMs = MIN_LOADING_MS): Promise { const start = Date.now(); const result = await promise; const elapsed = Date.now() - start; if (elapsed < minMs) { await new Promise((r) => setTimeout(r, minMs - elapsed)); } return result; } function SettingsUI({ initialSettings }: { initialSettings: Settings }) { const toastRef = React.useRef(null); const { push, portal } = useToaster(toastRef.current); const [aLabel, setALabel] = React.useState(initialSettings.palette[0]?.label || 'Primary'); const [aHex, setAHex] = React.useState(initialSettings.palette[0]?.hex || '#18181b'); const [bLabel, setBLabel] = React.useState(initialSettings.palette[1]?.label || 'Secondary'); const [bHex, setBHex] = React.useState(initialSettings.palette[1]?.hex || '#71717a'); const [cLabel, setCLabel] = React.useState(initialSettings.palette[2]?.label || 'Accent'); const [cHex, setCHex] = React.useState(initialSettings.palette[2]?.hex || '#4f46e5'); const [def, setDef] = React.useState(initialSettings.defaultToken || 'A'); const [saving, setSaving] = React.useState(false); const [restoring, setRestoring] = React.useState(false); const restBase = (window as any).openicon_api?.root?.replace(/\/$/, '') || '/wp-json'; const nonce = (window as any).openicon_api?.nonce || ''; const apiBase = `${restBase}/openicon/v1`; // Premium provider info for upsell const premiumProviders = [ { key: 'lucide', label: 'Lucide Icons', count: '1,500+' }, { key: 'tabler', label: 'Tabler Icons', count: '5,200+' }, ]; // Save settings const handleSave = React.useCallback(async () => { setSaving(true); try { const settings = { activeProvider: 'heroicons', pinnedVersion: 'latest', palette: [ { token: 'A', label: aLabel, hex: aHex }, { token: 'B', label: bLabel, hex: bHex }, { token: 'C', label: cLabel, hex: cHex }, ], defaultToken: def, }; const response = await withMinDelay( fetch(`${apiBase}/settings`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce, }, body: JSON.stringify(settings), }) ); const data = await response.json(); if (response.ok && data.success) { push({ type: 'success', title: 'Saved', message: 'Settings updated.', }); } else { throw new Error(data.message || 'Failed to save'); } } catch { push({ type: 'error', title: 'Error', message: 'Failed to save settings.', }); } finally { setSaving(false); } }, [aLabel, aHex, bLabel, bHex, cLabel, cHex, def, apiBase, nonce, push]); // Restore defaults const handleRestore = React.useCallback(async () => { setRestoring(true); try { const response = await withMinDelay( fetch(`${apiBase}/settings/restore`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce, }, }) ); const data = await response.json(); if (response.ok && data.success) { // Update local state with defaults setALabel('Primary'); setAHex('#18181b'); setBLabel('Secondary'); setBHex('#71717a'); setCLabel('Accent'); setCHex('#4f46e5'); setDef('A'); push({ type: 'success', title: 'Restored', message: 'Defaults restored.', }); } else { throw new Error('Failed to restore'); } } catch { push({ type: 'error', title: 'Error', message: 'Failed to restore defaults.', }); } finally { setRestoring(false); } }, [apiBase, nonce, push]); const controlClass = 'max-w-[520px]'; return (
{portal}
{/* Upgrade Banner */} Unlock More Icons Upgrade to ACF Open Icons Premium for access to 6,000+ icons
{premiumProviders.map((p) => ( {p.label} ({p.count}) ))}

Plus: Provider switching, icon migration tools, and priority support.

Get Premium
{/* Icon Set */}
{}} items={[ { value: 'heroicons', label: 'Heroicons' }, { value: 'lucide', label: 'Lucide Icons', disabled: true, badge: 'Premium' }, { value: 'tabler', label: 'Tabler Icons', disabled: true, badge: 'Premium' }, { value: 'custom', label: 'Custom Icons', disabled: true, badge: 'Premium' }, ]} className={controlClass} />
{/* Palette Colours */}
Token A setALabel(e.target.value)} className={controlClass} /> setAHex(e.target.value)} />
Token B setBLabel(e.target.value)} className={controlClass} /> setBHex(e.target.value)} />
Token C setCLabel(e.target.value)} className={controlClass} /> setCHex(e.target.value)} />
{/* Action Buttons */}
{/* Toast container - below buttons */}
); } function mount() { const wrap = document.querySelector('.wrap'); if (!wrap) { return; } // Get initial settings from the global variable set by PHP const initialSettings: Settings = (window as any).__OPENICON_SETTINGS__ || { activeProvider: 'heroicons', pinnedVersion: 'latest', palette: [ { token: 'A', label: 'Primary', hex: '#18181b' }, { token: 'B', label: 'Secondary', hex: '#71717a' }, { token: 'C', label: 'Accent', hex: '#4f46e5' }, ], defaultToken: 'A', }; // Hide the PHP-rendered forms const forms = wrap.querySelectorAll('form'); forms.forEach((form) => { (form as HTMLElement).style.display = 'none'; }); const existing = wrap.querySelector('.openicon-settings-ui'); if (existing) { return; // avoid duplicates } const mountEl = document.createElement('div'); wrap.appendChild(mountEl); createRoot(mountEl).render(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', mount); } else { mount(); }