import { useRef, useState } from 'react' import { useCatalog } from './hooks/useCatalog' import { useCarts } from './hooks/useCarts' import { SectionBlock } from './components/SectionBlock' import { AddSectionDialog } from './components/AddSectionDialog' import type { Product, Drone } from './types' const THEMES = [ { value: '', label: 'OS default' }, { value: 'sonokai-default', label: 'Sonokai Default' }, { value: 'sonokai-default-darker', label: 'Sonokai Default Darker' }, { value: 'sonokai-shusia', label: 'Sonokai Shusia' }, { value: 'sonokai-andromeda', label: 'Sonokai Andromeda' }, { value: 'sonokai-atlantis', label: 'Sonokai Atlantis' }, { value: 'sonokai-maia', label: 'Sonokai Maia' }, { value: 'sonokai-espresso', label: 'Sonokai Espresso' }, { value: 'axis-dark', label: 'Axis Dark' }, { value: 'axis-light', label: 'Axis Light' }, ] const THEME_KEY = 'theme' function cartTotal(sections: { items: (Product | Drone)[] }[]): string { let total = 0 let currency = 'SEK' for (const section of sections) { for (const item of section.items) { if ('buildType' in item) { if (item.buildType === 'complete') { total += item.price.amount; currency = item.price.currency } else if (item.parts[0]) { total += item.parts.reduce((s, p) => s + p.price.amount, 0); currency = item.parts[0].price.currency } } else { total += item.price.amount currency = item.price.currency } } } return total === 0 ? '—' : new Intl.NumberFormat('en', { style: 'currency', currency }).format(total) } export default function App() { const [theme, setTheme] = useState(() => { const stored = localStorage.getItem(THEME_KEY) ?? '' if (stored) document.documentElement.setAttribute('data-theme', stored) return stored }) const [showAddSection, setShowAddSection] = useState(false) const [showNewCart, setShowNewCart] = useState(false) const [newCartName, setNewCartName] = useState('') const [cartToDelete, setCartToDelete] = useState(null) const [renamingCartId, setRenamingCartId] = useState(null) const [renameCartValue, setRenameCartValue] = useState('') const importRef = useRef(null) const { catalog, loading: catalogLoading, error: catalogError } = useCatalog() const { carts, activeCart, activeId, storageError, setActiveCart, createCart, deleteCart, renameCart, importCart, exportCart, addSection, removeSection, renameSection, reorderSections, addItem, updateItem, removeItem } = useCarts() const [dragSectionId, setDragSectionId] = useState(null) const [dragOverId, setDragOverId] = useState(null) function commitCartRename() { if (renamingCartId && renameCartValue.trim()) renameCart(renamingCartId, renameCartValue.trim()) setRenamingCartId(null) } function handleThemeChange(value: string) { setTheme(value) if (value) { localStorage.setItem(THEME_KEY, value); document.documentElement.setAttribute('data-theme', value) } else { localStorage.removeItem(THEME_KEY); document.documentElement.removeAttribute('data-theme') } } async function handleCreateFromTemplate(templateId: string) { const existing = carts.find(c => c.templateId === templateId) if (existing) { setActiveCart(existing.id); return } const template = catalog?.templates.find(t => t.id === templateId) if (!template) return await createCart(template.name, template) } async function handleCreateEmpty() { if (!newCartName.trim()) return await createCart(newCartName.trim()) setNewCartName('') setShowNewCart(false) } const selectStyle = { background: 'var(--color-bg1)', color: 'var(--color-fg)', borderColor: 'var(--color-bg3)' } return (
{/* Header */}

FPV Shop List

{activeCart && ( {cartTotal(activeCart.sections)} )} {activeCart && ( )}
{/* Cart bar */}
{carts.map(cart => (
{renamingCartId === cart.id ? ( setRenameCartValue(e.target.value)} onBlur={commitCartRename} onKeyDown={e => { if (e.key === 'Enter') commitCartRename(); if (e.key === 'Escape') setRenamingCartId(null) }} className="rounded border px-1 py-0.5 text-sm outline-none" style={{ background: 'var(--color-bg2)', color: 'var(--color-fg)', borderColor: 'var(--color-bg3)', width: `${Math.max(renameCartValue.length, 4)}ch` }} /> ) : ( )}
))} {/* New cart */} {showNewCart ? (
{ e.preventDefault(); handleCreateEmpty() }} className="flex items-center gap-1"> setNewCartName(e.target.value)} onBlur={() => { if (!newCartName.trim()) setShowNewCart(false) }} placeholder="Cart name" className="rounded border px-2 py-1 text-sm outline-none" style={{ background: 'var(--color-bg2)', color: 'var(--color-fg)', borderColor: 'var(--color-bg3)' }} />
) : ( )} {/* Import cart from file */} { const file = e.target.files?.[0] if (!file) return file.text().then(json => { importCart(json).then(ok => { if (!ok) alert('Invalid cart file.') }) }) e.target.value = '' }} />
{/* Template row */} {catalog && catalog.templates.length > 0 && (
Templates: {catalog.templates.map(t => ( ))}
)} {/* Main */}
{catalogLoading &&

Loading…

} {catalogError &&

{catalogError}

} {storageError &&

{storageError}

} {!activeCart && !catalogLoading && (

No cart yet — create one or load a template above

)} {activeCart && <> {activeCart.sections.map(section => ( removeItem(section.id, itemId)} onAddItem={item => addItem(section.id, item)} onEditItem={item => updateItem(section.id, item)} onRemoveSection={() => removeSection(section.id)} onRenameSection={label => renameSection(section.id, label)} isDragging={dragSectionId === section.id} isDragOver={dragOverId === section.id} onDragStart={() => setDragSectionId(section.id)} onDragOver={e => { e.preventDefault(); setDragOverId(section.id) }} onDrop={() => { if (dragSectionId) reorderSections(dragSectionId, section.id); setDragOverId(null) }} onDragEnd={() => { setDragSectionId(null); setDragOverId(null) }} /> ))} }
{showAddSection && ( addSection({ id, label, required })} onClose={() => setShowAddSection(false)} /> )} {cartToDelete && (() => { const cart = carts.find(c => c.id === cartToDelete) return (

Remove cart?

"{cart?.name}" will be permanently deleted.

) })()}
) }