/* global React */ const { useState: useStateS, useEffect: useEffectS, useMemo: useMemoS, useRef: useRefS } = React; // ============================================================================= // PHOTO SLOTS — slot_key → { label, recommended size, page } // ============================================================================= window.PHOTO_SLOTS = [ { key: 'hero-photo', label: 'Homepage Hero', page: 'Homepage', size: '800 × 1000 px' }, { key: 'press-on-set-01', label: 'Press-On Teaser 1', page: 'Homepage', size: '480 × 560 px' }, { key: 'press-on-set-02', label: 'Press-On Teaser 2', page: 'Homepage', size: '480 × 560 px' }, { key: 'gallery-1', label: 'Gallery 1 — Aurora Chrome', page: 'Gallery', size: '400 × 533 px' }, { key: 'gallery-2', label: 'Gallery 2 — Hand-painted', page: 'Gallery', size: '400 × 533 px' }, { key: 'gallery-3', label: 'Gallery 3 — Micro French', page: 'Gallery', size: '400 × 533 px' }, { key: 'gallery-4', label: 'Gallery 4 — Pearl Drop', page: 'Gallery', size: '400 × 533 px' }, { key: 'gallery-5', label: 'Gallery 5 — Cat Eye', page: 'Gallery', size: '400 × 533 px' }, { key: 'gallery-6', label: 'Gallery 6 — Butter Nude', page: 'Gallery', size: '400 × 533 px' }, { key: 'gallery-7', label: 'Gallery 7 — Cherry Blossom', page: 'Gallery', size: '400 × 533 px' }, { key: 'gallery-8', label: 'Gallery 8 — Mirror Chrome', page: 'Gallery', size: '400 × 533 px' }, { key: 'gallery-9', label: 'Gallery 9 — Lavender French', page: 'Gallery', size: '400 × 533 px' }, { key: 'gallery-10', label: 'Gallery 10 — Bow Charms', page: 'Gallery', size: '400 × 533 px' }, ]; // ============================================================================= // PHOTOS // ============================================================================= window.PhotosSection = function PhotosSection() { const [photos, setPhotos] = useStateS({}); const [loading, setLoading] = useStateS(true); const [uploadingKey, setUploadingKey] = useStateS(null); const [err, setErr] = useStateS(''); function load() { setLoading(true); window.LQ.request('/photos').then((p) => { setPhotos(p || {}); setLoading(false); }); } useEffectS(load, []); async function upload(slotKey, file, label) { setUploadingKey(slotKey); setErr(''); try { await window.LQ.uploadPhoto(slotKey, file, label); load(); } catch (e) { setErr(e.message); } finally { setUploadingKey(null); } } async function remove(slotKey) { if (!confirm('Remove this photo?')) return; await window.LQ.request(`/photos/${slotKey}`, { method: 'DELETE' }); load(); } const grouped = useMemoS(() => { const g = {}; window.PHOTO_SLOTS.forEach((s) => { (g[s.page] = g[s.page] || []).push(s); }); return g; }, []); if (loading) return
Loading…
; return ( <>

Upload real photos to replace placeholders on the public site. Use JPG, PNG, or WebP — max 8 MB. Aim for the recommended size for crisp display.

{err &&
{err}
} {Object.entries(grouped).map(([page, slots]) => (

{page}

{slots.filter((s) => photos[s.key]).length} / {slots.length} uploaded
{slots.map((s) => { const p = photos[s.key]; return (
{!p &&
No image
}
{s.label}
{s.size}
{p && }
); })}
))} ); }; // ============================================================================= // SHOP (Press-on products) // ============================================================================= const CATEGORIES = ['Chrome', 'Florals', 'French', 'Glitter', '3D Charms', 'Minimal']; const SHAPES = ['Almond', 'Coffin', 'Stiletto', 'Square', 'Short Square', 'Oval', 'Short Oval', 'Ballerina']; const TAG_OPTIONS = ['Bestseller', 'New', 'Bridal', 'Hand-painted']; window.ShopSection = function ShopSection() { const [list, setList] = useStateS([]); const [loading, setLoading] = useStateS(true); const [editing, setEditing] = useStateS(null); const [adding, setAdding] = useStateS(false); function load() { setLoading(true); window.LQ.request('/products').then((r) => { setList(r || []); setLoading(false); }); } useEffectS(load, []); async function del(id) { if (!confirm('Deactivate this product?')) return; await window.LQ.request(`/products/${id}`, { method: 'DELETE' }); load(); } if (loading) return
Loading…
; return ( <>

{list.length} products active.

{list.map((p) => ( ))}
PhotoNameCategoryShapePriceStockTagsActions
{p.name} {p.category} {p.shape} {window.LQ_fmtINR(p.price)} {p.stock} {(p.tags || []).map((t) => {t})}
{(editing || adding) && ( { setEditing(null); setAdding(false); }} onSaved={() => { setEditing(null); setAdding(false); load(); }} /> )} ); }; window.ProductModal = function ProductModal({ product, onClose, onSaved }) { const isEdit = !!product; const [form, setForm] = useStateS({ name: product?.name || '', category: product?.category || CATEGORIES[0], shape: product?.shape || SHAPES[0], price: product?.price || '', purchase_price: product?.purchase_price || '', stock: product?.stock || 0, low_stock_alert: product?.low_stock_alert || 3, tags: product?.tags || [], photo_path: product?.photo_path || '', is_active: product?.is_active ?? true, }); const [saving, setSaving] = useStateS(false); const [uploading, setUploading] = useStateS(false); const [err, setErr] = useStateS(''); function set(k, v) { setForm((f) => ({ ...f, [k]: v })); } function toggleTag(t) { setForm((f) => ({ ...f, tags: f.tags.includes(t) ? f.tags.filter((x) => x !== t) : [...f.tags, t] })); } async function uploadPhoto(file) { setUploading(true); setErr(''); try { const slotKey = `shop-product-${product?.id || Date.now()}`; const r = await window.LQ.uploadPhoto(slotKey, file, form.name || 'Product'); set('photo_path', r.file_path); } catch (e) { setErr(e.message); } finally { setUploading(false); } } async function save(e) { e.preventDefault(); setSaving(true); setErr(''); try { const body = { ...form }; body.price = Number(body.price); body.purchase_price = body.purchase_price === '' ? null : Number(body.purchase_price); body.stock = Number(body.stock); body.low_stock_alert = Number(body.low_stock_alert); if (isEdit) await window.LQ.request(`/products/${product.id}`, { method: 'PATCH', body }); else await window.LQ.request('/products', { method: 'POST', body }); onSaved(); } catch (e) { setErr(e.message); } finally { setSaving(false); } } const margin = (form.price && form.purchase_price) ? Math.round(((form.price - form.purchase_price) / form.price) * 100) : null; return (
e.stopPropagation()}>

{isEdit ? 'Edit Product' : 'Add Product'}

set('name', e.target.value)} />
set('price', e.target.value)} />
set('purchase_price', e.target.value)} placeholder="optional" />
set('stock', e.target.value)} />
set('low_stock_alert', e.target.value)} />
{margin !== null && (
Margin: {margin}% ({window.LQ_fmtINR(form.price - form.purchase_price)} per unit)
)}
{TAG_OPTIONS.map((t) => ( ))}
{!form.photo_path &&
{uploading ? 'Uploading…' : 'Click to upload'}
} e.target.files[0] && uploadPhoto(e.target.files[0])} />
{err &&
{err}
}
); }; // ============================================================================= // INVENTORY (compact view, inline editing) // ============================================================================= window.InventorySection = function InventorySection() { const [list, setList] = useStateS([]); const [loading, setLoading] = useStateS(true); function load() { setLoading(true); window.LQ.request('/products').then((r) => { setList(r || []); setLoading(false); }); } useEffectS(load, []); async function update(id, patch) { await window.LQ.request(`/products/${id}`, { method: 'PATCH', body: patch }); load(); } if (loading) return
Loading…
; return ( <>

Adjust stock and pricing inline. Stock auto-decrements when an order ships.

{list.map((p) => { const margin = (p.price && p.purchase_price) ? Math.round(((p.price - p.purchase_price) / p.price) * 100) : '—'; return ( ); })}
NameCostPriceMarginStockAlert at
{p.name}
{p.category}
update(p.id, { purchase_price: e.target.value === '' ? null : Number(e.target.value) })} /> update(p.id, { price: Number(e.target.value) })} /> {typeof margin === 'number' ? margin + '%' : margin}
update(p.id, { stock: Number(e.target.value) })} />
update(p.id, { low_stock_alert: Number(e.target.value) })} />
); }; // ============================================================================= // SHIPPING (orders + provider toggle) // ============================================================================= window.ShippingSection = function ShippingSection() { const [orders, setOrders] = useStateS([]); const [shipping, setShipping] = useStateS(null); const [loading, setLoading] = useStateS(true); function load() { setLoading(true); Promise.all([ window.LQ.request('/orders'), window.LQ.request('/settings/shipping').catch(() => ({ provider: 'manual' })), ]).then(([o, s]) => { setOrders(o || []); setShipping(s || { provider: 'manual' }); setLoading(false); }); } useEffectS(load, []); async function setStatus(id, status) { await window.LQ.request(`/orders/${id}`, { method: 'PATCH', body: { status } }); load(); } async function printLabel(o) { if (shipping.provider === 'manual') { window.LQ_printManualLabel(o, shipping); await window.LQ.request(`/orders/${o.id}`, { method: 'PATCH', body: { label_printed: true } }); load(); return; } // Shiprocket flow — placeholder until live integration alert(`Shiprocket label for ${o.order_code} will be fetched from API once credentials are saved + tested.`); await window.LQ.request(`/orders/${o.id}`, { method: 'PATCH', body: { label_printed: true } }); load(); } async function saveShipping() { await window.LQ.request('/settings/shipping', { method: 'PUT', body: shipping }); alert('Shipping settings saved.'); } function set(k, v) { setShipping({ ...shipping, [k]: v }); } if (loading) return
Loading…
; return ( <>

Shipping Provider

{shipping.provider === 'manual' && <>

Used as the FROM address on every shipping label you print. Update this once — it appears on every label.

set('from_name', e.target.value)} placeholder="Lacquery Nails" />
set('from_phone', e.target.value)} placeholder="+91 83174 51779" />