From 27093bcd54ae72e75eb9409736843e3ed548858d Mon Sep 17 00:00:00 2001 From: chris Date: Sat, 18 Apr 2026 09:44:00 -0400 Subject: [PATCH] fix: multi-category checkboxes in admin + requires-delivery toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Category selector replaced with checkboxes — items can now be assigned to multiple categories directly in admin (not just Square). Each category shows a "Square" label if it came from the Square assignment. Saves as categoriesOverride[] (array of category names). - categoriesOverride takes precedence over old categoryOverride in the catalog route; old overrides still work as fallback. - Requires-delivery toggle and custom rate fields were already in the code but needed container rebuild to appear — no logic change. Co-Authored-By: Claude Sonnet 4.6 --- estore/src/app/admin/page.tsx | 69 +++++++++++++++-------------- estore/src/app/api/catalog/route.ts | 31 +++++++++---- estore/src/lib/overrides.ts | 2 + 3 files changed, 61 insertions(+), 41 deletions(-) diff --git a/estore/src/app/admin/page.tsx b/estore/src/app/admin/page.tsx index a0df5b1..6ff5789 100644 --- a/estore/src/app/admin/page.tsx +++ b/estore/src/app/admin/page.tsx @@ -728,10 +728,12 @@ function ItemEditor({ }) { const ov = item._override - const [hidden, setHidden] = useState(ov.hidden ?? false) - const [featured, setFeatured] = useState(ov.featured ?? item.featured ?? false) - const [catOverride, setCatOverride] = useState(ov.categoryOverride ?? '') - const [catLabel, setCatLabel] = useState(ov.categoryLabelOverride ?? '') + const [hidden, setHidden] = useState(ov.hidden ?? false) + const [featured, setFeatured] = useState(ov.featured ?? item.featured ?? false) + // Multi-category selection: stores category names (labels). Initialise from new override or fall back to Square assignment. + const [selectedCatNames, setSelectedCatNames] = useState( + ov.categoriesOverride ?? item.categoryLabels ?? [item.categoryLabel] + ) const [sortOrder, setSortOrder] = useState(String(ov.sortOrder ?? '')) const [showColors, setShowColors] = useState( ov.showColors != null ? ov.showColors : null @@ -796,8 +798,8 @@ function ItemEditor({ hiddenVariationIds: hiddenVars, hiddenModifierIds: hiddenMods, } - if (catOverride) patch.categoryOverride = catOverride - if (catLabel) patch.categoryLabelOverride = catLabel + // Always save categoriesOverride (replaces old single-field overrides) + patch.categoriesOverride = selectedCatNames if (sortOrder !== '') patch.sortOrder = Number(sortOrder) if (showColors !== null) patch.showColors = showColors if (descOverride) patch.descriptionOverride = descOverride @@ -833,8 +835,7 @@ function ItemEditor({ if (res.ok) { setHidden(false) setFeatured(item.featured ?? false) - setCatOverride('') - setCatLabel('') + setSelectedCatNames(item.categoryLabels ?? [item.categoryLabel]) setSortOrder('') setShowColors(null) setHiddenMods([]) @@ -868,15 +869,13 @@ function ItemEditor({ if (!newCatName.trim()) return setCreatingCat(true) const cat = await onCreateCategory(newCatName.trim()) - setCatOverride(cat.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')) - setCatLabel(cat.name) + // Auto-select the newly created category + if (cat.id) setSelectedCatNames((prev) => [...prev, cat.name]) setNewCatName('') setShowNewCat(false) setCreatingCat(false) } - const catSlug = (name: string) => name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') - return (
@@ -951,27 +950,31 @@ function ItemEditor({
)} - {/* Category */} + {/* Category — multi-select checkboxes */}
- -
-
- -
+ +
+ {categories.map((c) => ( + + ))} + {categories.length === 0 && ( +

No categories found — refresh from Square.

+ )}
{showNewCat && (
diff --git a/estore/src/app/api/catalog/route.ts b/estore/src/app/api/catalog/route.ts index 68c6d65..76deca5 100644 --- a/estore/src/app/api/catalog/route.ts +++ b/estore/src/app/api/catalog/route.ts @@ -13,14 +13,29 @@ function applyOverrides(items: CatalogItem[]): CatalogItem[] { return { ...item, featured: ov.featured ?? item.featured, - category: ov.categoryOverride ?? item.category, - categoryLabel: ov.categoryLabelOverride ?? item.categoryLabel, - categories: ov.categoryOverride - ? [ov.categoryOverride, ...(item.categories ?? [item.category]).slice(1)] - : (item.categories ?? [item.category]), - categoryLabels: ov.categoryLabelOverride - ? [ov.categoryLabelOverride, ...(item.categoryLabels ?? [item.categoryLabel]).slice(1)] - : (item.categoryLabels ?? [item.categoryLabel]), + // categoriesOverride (array of names) takes precedence over the old single-field overrides + ...(ov.categoriesOverride?.length + ? (() => { + const toSlug = (n: string) => n.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') + const cats = ov.categoriesOverride!.map(toSlug) + return { + categories: cats, + categoryLabels: ov.categoriesOverride!, + category: cats[0], + categoryLabel: ov.categoriesOverride![0], + } + })() + : { + category: ov.categoryOverride ?? item.category, + categoryLabel: ov.categoryLabelOverride ?? item.categoryLabel, + categories: ov.categoryOverride + ? [ov.categoryOverride, ...(item.categories ?? [item.category]).slice(1)] + : (item.categories ?? [item.category]), + categoryLabels: ov.categoryLabelOverride + ? [ov.categoryLabelOverride, ...(item.categoryLabels ?? [item.categoryLabel]).slice(1)] + : (item.categoryLabels ?? [item.categoryLabel]), + } + ), showColors: ov.showColors != null ? ov.showColors : item.showColors, colorMin: ov.colorMin ?? item.colorMin, colorMax: ov.colorMax !== undefined ? ov.colorMax : item.colorMax, diff --git a/estore/src/lib/overrides.ts b/estore/src/lib/overrides.ts index e453dc8..84073d3 100644 --- a/estore/src/lib/overrides.ts +++ b/estore/src/lib/overrides.ts @@ -24,6 +24,8 @@ export interface ItemOverride { disabledColors?: string[] /** Unit label for the quantity field, e.g. "ft". When set, the quantity control shows "X ft". */ quantityUnit?: string + /** Override the full list of display categories (stores category NAMES/labels). Replaces categoryOverride + categoryLabelOverride. */ + categoriesOverride?: string[] | null /** When true, pickup is not offered — item must be delivered. */ requiresDelivery?: boolean /** Override delivery base charge in cents for this item (replaces the tier default). */