@hectasquare/medusa-plugin-category-tree 1.0.10 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -155,7 +155,7 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
155
155
  const currentOrder = products.map((p) => p.id);
156
156
  await persistProductConfig(currentOrder, next);
157
157
  };
158
- // Remove products from category
158
+ // Remove products from category using official Medusa v2 JS SDK method
159
159
  const handleRemoveProducts = async (idsToRemove) => {
160
160
  if (idsToRemove.length === 0)
161
161
  return;
@@ -166,9 +166,8 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
166
166
  return;
167
167
  setIsRemoving(true);
168
168
  try {
169
- await sdk_1.sdk.client.fetch(`/admin/product-categories/${categoryId}/products`, {
170
- method: "POST",
171
- body: { remove: idsToRemove },
169
+ await sdk_1.sdk.admin.productCategory.updateProducts(categoryId, {
170
+ remove: idsToRemove,
172
171
  });
173
172
  ui_1.toast.success(`Removed ${idsToRemove.length} product(s) from category`);
174
173
  setSelectedIds(new Set());
@@ -210,15 +209,15 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
210
209
  searchCatalog(searchQuery, catalogPage);
211
210
  }
212
211
  }, [focusModalOpen, searchQuery, catalogPage, searchCatalog]);
212
+ // Save added products using official Medusa v2 JS SDK method
213
213
  const handleSaveAddedProducts = async () => {
214
214
  const idsToAdd = Array.from(catalogSelectedIds);
215
215
  if (idsToAdd.length === 0)
216
216
  return;
217
217
  setIsAdding(true);
218
218
  try {
219
- await sdk_1.sdk.client.fetch(`/admin/product-categories/${categoryId}/products`, {
220
- method: "POST",
221
- body: { add: idsToAdd },
219
+ await sdk_1.sdk.admin.productCategory.updateProducts(categoryId, {
220
+ add: idsToAdd,
222
221
  });
223
222
  ui_1.toast.success(`Added ${idsToAdd.length} product(s) to ${categoryName}`);
224
223
  setFocusModalOpen(false);
@@ -243,18 +242,39 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
243
242
  }
244
243
  return products;
245
244
  }, [products, activeView, topSellerIds]);
246
- return ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between flex-wrap gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 p-1 rounded-lg bg-ui-bg-subtle border border-ui-border-base", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => setActiveView("all"), className: `px-2.5 py-1 text-xs font-medium rounded-md transition-colors ${activeView === "all"
245
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between flex-wrap gap-2", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1.5 p-1 rounded-lg bg-ui-bg-subtle border border-ui-border-base", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: (e) => {
246
+ e.preventDefault();
247
+ e.stopPropagation();
248
+ setActiveView("all");
249
+ }, className: `px-2.5 py-1 text-xs font-medium rounded-md transition-colors ${activeView === "all"
247
250
  ? "bg-ui-bg-base text-ui-fg-base shadow-2xs font-semibold"
248
- : "text-ui-fg-muted hover:text-ui-fg-base"}`, children: ["All Products (", products.length, ")"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => setActiveView("top_sellers"), className: `flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md transition-colors ${activeView === "top_sellers"
251
+ : "text-ui-fg-muted hover:text-ui-fg-base"}`, children: ["All Products (", products.length, ")"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: (e) => {
252
+ e.preventDefault();
253
+ e.stopPropagation();
254
+ setActiveView("top_sellers");
255
+ }, className: `flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md transition-colors ${activeView === "top_sellers"
249
256
  ? "bg-amber-500/10 text-amber-700 dark:text-amber-400 font-semibold"
250
- : "text-ui-fg-muted hover:text-amber-600"}`, children: [(0, jsx_runtime_1.jsx)(icons_1.Sparkles, { className: "w-3.5 h-3.5 text-amber-500" }), (0, jsx_runtime_1.jsxs)("span", { children: ["Top Commercial Sellers (", topSellerIds.size, ")"] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [selectedIds.size > 0 && ((0, jsx_runtime_1.jsxs)(ui_1.Button, { size: "small", variant: "danger", disabled: isRemoving, onClick: () => handleRemoveProducts(Array.from(selectedIds)), className: "flex items-center gap-1 text-xs", children: [(0, jsx_runtime_1.jsx)(icons_1.Trash, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsxs)("span", { children: ["Remove Selected (", selectedIds.size, ")"] })] })), (0, jsx_runtime_1.jsxs)(ui_1.Button, { size: "small", variant: "secondary", onClick: () => {
257
+ : "text-ui-fg-muted hover:text-amber-600"}`, children: [(0, jsx_runtime_1.jsx)(icons_1.Sparkles, { className: "w-3.5 h-3.5 text-amber-500" }), (0, jsx_runtime_1.jsxs)("span", { children: ["Top Commercial Sellers (", topSellerIds.size, ")"] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [selectedIds.size > 0 && ((0, jsx_runtime_1.jsxs)(ui_1.Button, { type: "button", size: "small", variant: "danger", disabled: isRemoving, onClick: (e) => {
258
+ e.preventDefault();
259
+ e.stopPropagation();
260
+ handleRemoveProducts(Array.from(selectedIds));
261
+ }, className: "flex items-center gap-1 text-xs", children: [(0, jsx_runtime_1.jsx)(icons_1.Trash, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsxs)("span", { children: ["Remove Selected (", selectedIds.size, ")"] })] })), (0, jsx_runtime_1.jsxs)(ui_1.Button, { type: "button", size: "small", variant: "secondary", onClick: (e) => {
262
+ e.preventDefault();
263
+ e.stopPropagation();
251
264
  setCatalogSelectedIds(new Set());
252
265
  setSearchQuery("");
253
266
  setCatalogPage(0);
254
267
  setFocusModalOpen(true);
255
268
  }, className: "flex items-center gap-1 text-xs", children: [(0, jsx_runtime_1.jsx)(icons_1.Plus, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: "Add Products" })] })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "border border-ui-border-base rounded-xl overflow-hidden bg-ui-bg-base shadow-2xs", children: isLoading ? ((0, jsx_runtime_1.jsx)("div", { className: "p-8 text-center text-xs text-ui-fg-muted", children: "Loading products\u2026" })) : displayedProducts.length === 0 ? ((0, jsx_runtime_1.jsxs)("div", { className: "p-8 text-center flex flex-col items-center justify-center gap-2", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-xs text-ui-fg-muted", children: activeView === "top_sellers"
256
269
  ? "No products marked as Top Commercial Sellers yet. Star a product below to highlight it."
257
- : "No products assigned to this category." }), activeView === "all" && ((0, jsx_runtime_1.jsxs)(ui_1.Button, { size: "small", variant: "secondary", onClick: () => setFocusModalOpen(true), className: "flex items-center gap-1 text-xs", children: [(0, jsx_runtime_1.jsx)(icons_1.Plus, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: "Add Products" })] }))] })) : ((0, jsx_runtime_1.jsxs)("table", { className: "w-full text-left text-xs border-collapse", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { className: "border-b border-ui-border-base bg-ui-bg-subtle/60 text-ui-fg-muted font-medium", children: [(0, jsx_runtime_1.jsx)("th", { className: "py-2.5 px-3 w-8", children: (0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: displayedProducts.length > 0 && selectedIds.size === displayedProducts.length, onChange: () => {
270
+ : "No products assigned to this category." }), activeView === "all" && ((0, jsx_runtime_1.jsxs)(ui_1.Button, { type: "button", size: "small", variant: "secondary", onClick: (e) => {
271
+ e.preventDefault();
272
+ e.stopPropagation();
273
+ setCatalogSelectedIds(new Set());
274
+ setSearchQuery("");
275
+ setCatalogPage(0);
276
+ setFocusModalOpen(true);
277
+ }, className: "flex items-center gap-1 text-xs", children: [(0, jsx_runtime_1.jsx)(icons_1.Plus, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: "Add Products" })] }))] })) : ((0, jsx_runtime_1.jsxs)("table", { className: "w-full text-left text-xs border-collapse", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { className: "border-b border-ui-border-base bg-ui-bg-subtle/60 text-ui-fg-muted font-medium", children: [(0, jsx_runtime_1.jsx)("th", { className: "py-2.5 px-3 w-8", children: (0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: displayedProducts.length > 0 && selectedIds.size === displayedProducts.length, onChange: () => {
258
278
  if (selectedIds.size === displayedProducts.length)
259
279
  setSelectedIds(new Set());
260
280
  else
@@ -279,22 +299,26 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
279
299
  e.preventDefault();
280
300
  e.stopPropagation();
281
301
  handleMoveProduct(index, "down");
282
- }, className: "text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none cursor-pointer", children: "\u25BC" })] })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 min-w-0", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-9 h-9 rounded-md border border-ui-border-base overflow-hidden bg-ui-bg-subtle flex-shrink-0 flex items-center justify-center", children: p.thumbnail ? ((0, jsx_runtime_1.jsx)("img", { src: p.thumbnail, alt: p.title, className: "w-full h-full object-cover" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-[9px] text-ui-fg-muted", children: "IMG" })) }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col min-w-0", children: (0, jsx_runtime_1.jsx)("span", { className: "font-semibold text-ui-fg-base truncate leading-snug", title: p.title, children: p.title }) })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: (e) => {
302
+ }, className: "text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none cursor-pointer", children: "\u25BC" })] })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 min-w-0", children: [(0, jsx_runtime_1.jsx)("a", { href: `/app/products/${p.id}`, target: "_blank", rel: "noopener noreferrer", className: "w-9 h-9 rounded-md border border-ui-border-base overflow-hidden bg-ui-bg-subtle flex-shrink-0 flex items-center justify-center hover:opacity-80 transition-opacity", title: `Open ${p.title} in new tab`, children: p.thumbnail ? ((0, jsx_runtime_1.jsx)("img", { src: p.thumbnail, alt: p.title, className: "w-full h-full object-cover" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-[9px] text-ui-fg-muted", children: "IMG" })) }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col min-w-0", children: (0, jsx_runtime_1.jsxs)("a", { href: `/app/products/${p.id}`, target: "_blank", rel: "noopener noreferrer", className: "font-semibold text-ui-fg-base hover:text-ui-fg-interactive truncate leading-snug hover:underline flex items-center gap-1 group/pname", title: `Open ${p.title} in new tab`, children: [(0, jsx_runtime_1.jsx)("span", { className: "truncate", children: p.title }), (0, jsx_runtime_1.jsx)(icons_1.ArrowUpRightMicro, { className: "w-3.5 h-3.5 text-ui-fg-muted group-hover/pname:text-ui-fg-interactive transition-colors shrink-0" })] }) })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: (e) => {
283
303
  e.preventDefault();
284
304
  e.stopPropagation();
285
305
  handleToggleTopSeller(p.id, p.title);
286
- }, className: `inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-semibold border transition-all ${isTopSeller
306
+ }, className: `inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-semibold border transition-all cursor-pointer ${isTopSeller
287
307
  ? "bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-300 dark:border-amber-700 shadow-2xs"
288
308
  : "bg-ui-bg-subtle text-ui-fg-muted border-ui-border-base hover:text-amber-600 hover:border-amber-300"}`, title: isTopSeller
289
309
  ? "Click to remove from Top Commercial Sellers"
290
- : "Click to mark as Top Commercial Seller (Reflected in Storefront 2)", children: [(0, jsx_runtime_1.jsx)("span", { children: isTopSeller ? "★" : "☆" }), (0, jsx_runtime_1.jsx)("span", { children: isTopSeller ? "Top Seller" : "Normal" })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3 text-ui-fg-muted", children: p.collection?.title || "-" }), (0, jsx_runtime_1.jsxs)("td", { className: "py-2.5 px-3 text-ui-fg-subtle", children: [p.variants?.length || 0, " variant(s)"] }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsx)("span", { className: `inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium ${p.status === "published"
310
+ : "Click to mark as Top Commercial Seller (Reflected on Storefront 2)", children: [(0, jsx_runtime_1.jsx)("span", { children: isTopSeller ? "★" : "☆" }), (0, jsx_runtime_1.jsx)("span", { children: isTopSeller ? "Top Seller" : "Normal" })] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3 text-ui-fg-muted", children: p.collection?.title || "-" }), (0, jsx_runtime_1.jsxs)("td", { className: "py-2.5 px-3 text-ui-fg-subtle", children: [p.variants?.length || 0, " variant(s)"] }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsx)("span", { className: `inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium ${p.status === "published"
291
311
  ? "bg-emerald-50 text-emerald-700 border border-emerald-200 dark:bg-emerald-950/50 dark:text-emerald-300"
292
312
  : "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400"}`, children: p.status }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3 text-right", children: (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Remove from category", onClick: (e) => {
293
313
  e.preventDefault();
294
314
  e.stopPropagation();
295
315
  handleRemoveProducts([p.id]);
296
- }, className: "p-1 text-ui-fg-muted hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/50 rounded transition-colors", children: (0, jsx_runtime_1.jsx)(icons_1.Trash, { className: "w-3.5 h-3.5" }) }) })] }, p.id));
297
- }) })] })) }), focusModalOpen && ((0, jsx_runtime_1.jsxs)("div", { className: "fixed inset-0 z-50 flex flex-col bg-white dark:bg-neutral-950 animate-in fade-in-50", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between px-6 py-3 border-b border-ui-border-base bg-ui-bg-base flex-shrink-0", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setFocusModalOpen(false), className: "p-1.5 rounded-lg text-ui-fg-muted hover:text-ui-fg-base hover:bg-ui-bg-subtle transition-colors", title: "Close (ESC)", children: (0, jsx_runtime_1.jsx)("span", { className: "text-sm font-bold", children: "\u2715" }) }), (0, jsx_runtime_1.jsx)("span", { className: "text-[11px] font-mono font-medium text-ui-fg-muted px-1.5 py-0.5 rounded bg-ui-bg-subtle border border-ui-border-base", children: "ESC" }), (0, jsx_runtime_1.jsxs)("h2", { className: "text-sm font-semibold text-ui-fg-base", children: ["Add Products to Category: ", categoryName] })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "relative w-64", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "Search products\u2026", value: searchQuery, onChange: (e) => {
316
+ }, className: "p-1 text-ui-fg-muted hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/50 rounded transition-colors cursor-pointer", children: (0, jsx_runtime_1.jsx)(icons_1.Trash, { className: "w-3.5 h-3.5" }) }) })] }, p.id));
317
+ }) })] })) }), focusModalOpen && ((0, jsx_runtime_1.jsxs)("div", { className: "fixed inset-0 z-50 flex flex-col bg-white dark:bg-neutral-950 animate-in fade-in-50", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between px-6 py-3 border-b border-ui-border-base bg-ui-bg-base flex-shrink-0", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: (e) => {
318
+ e.preventDefault();
319
+ e.stopPropagation();
320
+ setFocusModalOpen(false);
321
+ }, className: "p-1.5 rounded-lg text-ui-fg-muted hover:text-ui-fg-base hover:bg-ui-bg-subtle transition-colors cursor-pointer", title: "Close (ESC)", children: (0, jsx_runtime_1.jsx)("span", { className: "text-sm font-bold", children: "\u2715" }) }), (0, jsx_runtime_1.jsx)("span", { className: "text-[11px] font-mono font-medium text-ui-fg-muted px-1.5 py-0.5 rounded bg-ui-bg-subtle border border-ui-border-base", children: "ESC" }), (0, jsx_runtime_1.jsxs)("h2", { className: "text-sm font-semibold text-ui-fg-base", children: ["Add Products to Category: ", categoryName] })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "relative w-64", children: [(0, jsx_runtime_1.jsx)("input", { type: "text", placeholder: "Search products\u2026", value: searchQuery, onChange: (e) => {
298
322
  setSearchQuery(e.target.value);
299
323
  setCatalogPage(0);
300
324
  }, className: "w-full text-xs pl-8 pr-3 py-1.5 rounded-lg bg-ui-bg-subtle border border-ui-border-base text-ui-fg-base focus:bg-ui-bg-base focus:border-ui-border-interactive focus:outline-none placeholder:text-ui-fg-muted", autoFocus: true }), (0, jsx_runtime_1.jsx)(icons_1.MagnifyingGlass, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-ui-fg-muted pointer-events-none" })] }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "flex-1 overflow-y-auto px-6 py-4", children: (0, jsx_runtime_1.jsx)("div", { className: "border border-ui-border-base rounded-xl overflow-hidden bg-ui-bg-base shadow-sm", children: (0, jsx_runtime_1.jsxs)("table", { className: "w-full text-left text-xs border-collapse", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { className: "border-b border-ui-border-base bg-ui-bg-subtle/80 text-ui-fg-muted font-medium", children: [(0, jsx_runtime_1.jsx)("th", { className: "py-2.5 px-3 w-8", children: (0, jsx_runtime_1.jsx)("input", { type: "checkbox", checked: catalogProducts.length > 0 &&
@@ -338,6 +362,22 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
338
362
  }, className: "rounded border-ui-border-base cursor-pointer" }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2.5 min-w-0", children: [(0, jsx_runtime_1.jsx)("div", { className: "w-8 h-8 rounded border border-ui-border-base overflow-hidden bg-ui-bg-subtle flex-shrink-0 flex items-center justify-center", children: p.thumbnail ? ((0, jsx_runtime_1.jsx)("img", { src: p.thumbnail, alt: p.title, className: "w-full h-full object-cover" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-[8px] text-ui-fg-muted", children: "IMG" })) }), (0, jsx_runtime_1.jsx)("span", { className: "font-semibold text-ui-fg-base truncate leading-snug", children: p.title }), isAlreadyAssigned && ((0, jsx_runtime_1.jsx)(ui_1.Badge, { size: "small", color: "grey", className: "ml-1 text-[9px]", children: "Already in Category" }))] }) }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3 text-ui-fg-muted", children: p.collection?.title || "-" }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsx)("div", { className: "flex items-center gap-1 flex-wrap", children: (p.sales_channels || []).map((sc) => ((0, jsx_runtime_1.jsx)("span", { className: "px-1.5 py-0.5 rounded text-[10px] bg-ui-bg-subtle border border-ui-border-base text-ui-fg-subtle", children: sc.name }, sc.id))) }) }), (0, jsx_runtime_1.jsxs)("td", { className: "py-2.5 px-3 text-ui-fg-subtle", children: [p.variants?.length || 0, " variant(s)"] }), (0, jsx_runtime_1.jsx)("td", { className: "py-2.5 px-3", children: (0, jsx_runtime_1.jsx)("span", { className: `inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium ${p.status === "published"
339
363
  ? "bg-emerald-50 text-emerald-700 border border-emerald-200 dark:bg-emerald-950/50 dark:text-emerald-300"
340
364
  : "bg-neutral-100 text-neutral-600 dark:bg-neutral-800 dark:text-neutral-400"}`, children: p.status }) })] }, p.id));
341
- })) })] }) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between px-6 py-3 border-t border-ui-border-base bg-ui-bg-base flex-shrink-0", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-xs text-ui-fg-muted", children: ["Showing ", Math.min(catalogProducts.length, PAGE_SIZE), " of ", catalogTotalCount, " results"] }), (0, jsx_runtime_1.jsx)("span", { className: "text-xs text-ui-fg-muted", children: "\u2022" }), (0, jsx_runtime_1.jsxs)("span", { className: "text-xs font-semibold text-ui-fg-base", children: [catalogSelectedIds.size, " product(s) selected"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", disabled: catalogPage === 0, onClick: () => setCatalogPage((p) => Math.max(0, p - 1)), className: "px-2.5 py-1 text-xs rounded border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base disabled:opacity-40", children: "Prev" }), (0, jsx_runtime_1.jsx)("button", { type: "button", disabled: (catalogPage + 1) * PAGE_SIZE >= catalogTotalCount, onClick: () => setCatalogPage((p) => p + 1), className: "px-2.5 py-1 text-xs rounded border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base disabled:opacity-40", children: "Next" }), (0, jsx_runtime_1.jsx)("div", { className: "h-4 w-[1px] bg-ui-border-base mx-1" }), (0, jsx_runtime_1.jsx)(ui_1.Button, { size: "small", variant: "secondary", onClick: () => setFocusModalOpen(false), children: "Cancel" }), (0, jsx_runtime_1.jsxs)(ui_1.Button, { size: "small", variant: "primary", disabled: catalogSelectedIds.size === 0 || isAdding, onClick: handleSaveAddedProducts, className: "flex items-center gap-1 text-xs min-w-[70px] justify-center", children: [(0, jsx_runtime_1.jsx)(icons_1.CheckMini, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: isAdding ? "Saving…" : "Save" })] })] })] })] }))] }));
365
+ })) })] }) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between px-6 py-3 border-t border-ui-border-base bg-ui-bg-base flex-shrink-0", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-3", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-xs text-ui-fg-muted", children: ["Showing ", Math.min(catalogProducts.length, PAGE_SIZE), " of ", catalogTotalCount, " results"] }), (0, jsx_runtime_1.jsx)("span", { className: "text-xs text-ui-fg-muted", children: "\u2022" }), (0, jsx_runtime_1.jsxs)("span", { className: "text-xs font-semibold text-ui-fg-base", children: [catalogSelectedIds.size, " product(s) selected"] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", disabled: catalogPage === 0, onClick: (e) => {
366
+ e.preventDefault();
367
+ e.stopPropagation();
368
+ setCatalogPage((p) => Math.max(0, p - 1));
369
+ }, className: "px-2.5 py-1 text-xs rounded border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base disabled:opacity-40 cursor-pointer", children: "Prev" }), (0, jsx_runtime_1.jsx)("button", { type: "button", disabled: (catalogPage + 1) * PAGE_SIZE >= catalogTotalCount, onClick: (e) => {
370
+ e.preventDefault();
371
+ e.stopPropagation();
372
+ setCatalogPage((p) => p + 1);
373
+ }, className: "px-2.5 py-1 text-xs rounded border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base disabled:opacity-40 cursor-pointer", children: "Next" }), (0, jsx_runtime_1.jsx)("div", { className: "h-4 w-[1px] bg-ui-border-base mx-1" }), (0, jsx_runtime_1.jsx)(ui_1.Button, { type: "button", size: "small", variant: "secondary", onClick: (e) => {
374
+ e.preventDefault();
375
+ e.stopPropagation();
376
+ setFocusModalOpen(false);
377
+ }, children: "Cancel" }), (0, jsx_runtime_1.jsxs)(ui_1.Button, { type: "button", size: "small", variant: "primary", disabled: catalogSelectedIds.size === 0 || isAdding, onClick: (e) => {
378
+ e.preventDefault();
379
+ e.stopPropagation();
380
+ handleSaveAddedProducts();
381
+ }, className: "flex items-center gap-1 text-xs min-w-[70px] justify-center", children: [(0, jsx_runtime_1.jsx)(icons_1.CheckMini, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: isAdding ? "Saving…" : "Save" })] })] })] })] }))] }));
342
382
  };
343
383
  exports.CategoryProductsManager = CategoryProductsManager;
@@ -93,8 +93,13 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
93
93
  catch {
94
94
  // Endpoint fallback
95
95
  }
96
- // Metadata fallback: if metadata.images array exists
97
- if (metadata?.images && Array.isArray(metadata.images) && metadata.images.length > 0) {
96
+ // Explicit metadata.images check: if metadata.images array exists (even if empty)
97
+ if (metadata && Array.isArray(metadata.images)) {
98
+ if (metadata.images.length === 0) {
99
+ setCategoryImages([]);
100
+ setImageUrl("");
101
+ return;
102
+ }
98
103
  setCategoryImages(metadata.images);
99
104
  const thumb = metadata.images.find((img) => img.type === "thumbnail") || metadata.images[0];
100
105
  if (thumb?.url)
@@ -103,7 +108,7 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
103
108
  }
104
109
  // Legacy fallback
105
110
  const list = [];
106
- const thumb = currentImageUrl || metadata?.thumbnail || metadata?.image_url;
111
+ const thumb = metadata?.thumbnail || metadata?.image_url || currentImageUrl;
107
112
  if (thumb) {
108
113
  list.push({ url: thumb, type: "thumbnail" });
109
114
  }
@@ -228,8 +233,26 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
228
233
  setManualImageUrl("");
229
234
  ui_1.toast.success(`Added to gallery as ${newType === "thumbnail" ? "Thumbnail" : newType === "banner" ? "Hero Banner" : "Image"}`);
230
235
  };
231
- // Delete category image
236
+ // Delete category image with immediate persistence & explicit nulls
232
237
  const handleDeleteImage = async (img) => {
238
+ const remaining = categoryImages.filter((i) => i.url !== img.url && (img.id ? i.id !== img.id : true));
239
+ const nextThumb = remaining.find((i) => i.type === "thumbnail") || remaining[0] || null;
240
+ const nextBanner = remaining.find((i) => i.type === "banner") ||
241
+ (remaining.length > 1 ? remaining.find((i) => i.url !== nextThumb?.url) : null) ||
242
+ null;
243
+ // Update local state immediately
244
+ setCategoryImages(remaining);
245
+ setImageUrl(nextThumb ? nextThumb.url : "");
246
+ const updatedMeta = {
247
+ ...(currentMetadata || {}),
248
+ images: remaining,
249
+ thumbnail: nextThumb ? nextThumb.url : null,
250
+ image_url: nextThumb ? nextThumb.url : null,
251
+ banner: nextBanner ? nextBanner.url : null,
252
+ image: (nextBanner?.url || nextThumb?.url) || null,
253
+ };
254
+ setCurrentMetadata(updatedMeta);
255
+ // Call backend endpoint if DB relation exists
233
256
  if (img.id) {
234
257
  try {
235
258
  await sdk_1.sdk.client.fetch(`/admin/categories/${category.id}/images/batch`, {
@@ -240,16 +263,27 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
240
263
  },
241
264
  });
242
265
  }
243
- catch {
244
- // Fallback
266
+ catch (err) {
267
+ console.warn("Could not delete from backend images endpoint:", err);
245
268
  }
246
269
  }
247
- setCategoryImages((prev) => prev.filter((i) => i.url !== img.url && (img.id ? i.id !== img.id : true)));
248
- if (imageUrl === img.url) {
249
- const remaining = categoryImages.filter((i) => i.url !== img.url && (img.id ? i.id !== img.id : true));
250
- setImageUrl(remaining[0]?.url || "");
270
+ // Immediately persist metadata changes to Medusa
271
+ try {
272
+ await sdk_1.sdk.admin.productCategory.update(category.id, {
273
+ metadata: updatedMeta,
274
+ });
275
+ const updatedCat = {
276
+ ...category,
277
+ image_url: nextThumb ? nextThumb.url : "",
278
+ metadata: updatedMeta,
279
+ };
280
+ onSuccess(updatedCat);
281
+ ui_1.toast.success("Image removed and changes saved");
282
+ }
283
+ catch (err) {
284
+ console.warn("Could not immediately update category metadata on delete:", err);
285
+ ui_1.toast.info("Image removed from gallery (remember to save)");
251
286
  }
252
- ui_1.toast.info("Image removed");
253
287
  };
254
288
  if (!category)
255
289
  return null;
@@ -258,7 +292,7 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
258
292
  ? prev.filter((id) => id !== channelId)
259
293
  : [...prev, channelId]);
260
294
  };
261
- const handleAddYoutube = () => {
295
+ const handleAddYoutube = async () => {
262
296
  const raw = newYoutubeInput.trim();
263
297
  if (!raw)
264
298
  return;
@@ -272,15 +306,55 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
272
306
  ui_1.toast.error("Video already added");
273
307
  return;
274
308
  }
275
- setYoutubeUrls((prev) => [...prev, cleanUrl]);
309
+ const nextUrls = [...youtubeUrls, cleanUrl];
310
+ setYoutubeUrls(nextUrls);
276
311
  setNewYoutubeInput("");
277
- ui_1.toast.success("YouTube video added");
312
+ const updatedMeta = {
313
+ ...(currentMetadata || {}),
314
+ youtube_urls: nextUrls,
315
+ youtube_url: nextUrls[0] || null,
316
+ };
317
+ setCurrentMetadata(updatedMeta);
318
+ try {
319
+ await sdk_1.sdk.admin.productCategory.update(category.id, {
320
+ metadata: updatedMeta,
321
+ });
322
+ onSuccess({
323
+ ...category,
324
+ metadata: updatedMeta,
325
+ });
326
+ ui_1.toast.success("YouTube video added and saved");
327
+ }
328
+ catch {
329
+ ui_1.toast.success("YouTube video added");
330
+ }
278
331
  };
279
- const handleRemoveYoutube = (urlToRemove) => {
280
- setYoutubeUrls((prev) => prev.filter((u) => u !== urlToRemove));
332
+ const handleRemoveYoutube = async (urlToRemove) => {
333
+ const nextUrls = youtubeUrls.filter((u) => u !== urlToRemove);
334
+ setYoutubeUrls(nextUrls);
335
+ const updatedMeta = {
336
+ ...(currentMetadata || {}),
337
+ youtube_urls: nextUrls,
338
+ youtube_url: nextUrls[0] || null,
339
+ };
340
+ setCurrentMetadata(updatedMeta);
341
+ try {
342
+ await sdk_1.sdk.admin.productCategory.update(category.id, {
343
+ metadata: updatedMeta,
344
+ });
345
+ onSuccess({
346
+ ...category,
347
+ metadata: updatedMeta,
348
+ });
349
+ ui_1.toast.success("Video removed and changes saved");
350
+ }
351
+ catch {
352
+ ui_1.toast.info("Video removed (remember to save)");
353
+ }
281
354
  };
282
355
  const handleSubmit = async (e) => {
283
- e.preventDefault();
356
+ if (e && e.preventDefault)
357
+ e.preventDefault();
284
358
  if (!name.trim()) {
285
359
  ui_1.toast.error("Category name is required");
286
360
  return;
@@ -288,26 +362,22 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
288
362
  setIsSubmitting(true);
289
363
  try {
290
364
  const thumbImg = categoryImages.find((img) => img.type === "thumbnail") ||
291
- categoryImages[0];
365
+ categoryImages[0] ||
366
+ null;
292
367
  const bannerImg = categoryImages.find((img) => img.type === "banner") ||
293
- categoryImages.find((img) => img.url !== thumbImg?.url);
368
+ (categoryImages.length > 1 ? categoryImages.find((img) => img.url !== thumbImg?.url) : null) ||
369
+ null;
294
370
  const updatedMetadata = {
295
371
  ...(currentMetadata || {}),
296
372
  sales_channels: selectedChannels,
297
373
  youtube_urls: youtubeUrls,
374
+ youtube_url: youtubeUrls[0] || null,
298
375
  images: categoryImages,
376
+ thumbnail: thumbImg ? thumbImg.url : null,
377
+ image_url: thumbImg ? thumbImg.url : null,
378
+ banner: bannerImg ? bannerImg.url : null,
379
+ image: (bannerImg?.url || thumbImg?.url) || null,
299
380
  };
300
- if (thumbImg?.url) {
301
- updatedMetadata.thumbnail = thumbImg.url;
302
- updatedMetadata.image_url = thumbImg.url;
303
- }
304
- if (bannerImg?.url && bannerImg.url !== thumbImg?.url) {
305
- updatedMetadata.banner = bannerImg.url;
306
- updatedMetadata.image = bannerImg.url;
307
- }
308
- else if (thumbImg?.url) {
309
- updatedMetadata.image = thumbImg.url;
310
- }
311
381
  await sdk_1.sdk.admin.productCategory.update(category.id, {
312
382
  name: name.trim(),
313
383
  handle: handle.trim() || undefined,
@@ -322,7 +392,7 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
322
392
  name: name.trim(),
323
393
  handle: handle.trim(),
324
394
  description: description.trim() || null,
325
- image_url: imageUrl.trim() || category.image_url,
395
+ image_url: thumbImg ? thumbImg.url : "",
326
396
  rank: Number(rank) || 0,
327
397
  sales_channels: selectedChannels,
328
398
  is_active: isActive,
@@ -360,15 +430,15 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
360
430
  };
361
431
  return ((0, jsx_runtime_1.jsx)(ui_1.Drawer, { open: open, onOpenChange: onOpenChange, children: (0, jsx_runtime_1.jsx)(ui_1.Drawer.Content, { className: `transition-all duration-300 w-full z-50 ${isFullScreen
362
432
  ? "!max-w-full !w-screen !h-screen !inset-0 !rounded-none"
363
- : "max-w-5xl xl:max-w-6xl w-[92vw]"}`, children: (0, jsx_runtime_1.jsxs)("form", { onSubmit: handleSubmit, className: "flex flex-col h-full", children: [(0, jsx_runtime_1.jsxs)(ui_1.Drawer.Header, { className: "border-b border-ui-border-base pb-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between w-full", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(ui_1.Drawer.Title, { className: "text-base font-semibold text-ui-fg-base", children: category.name }), (0, jsx_runtime_1.jsxs)(ui_1.Drawer.Description, { className: "text-xs text-ui-fg-muted mt-0.5", children: ["Tier ", category.tier, " \u2022 ID: ", category.id] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => setIsFullScreen((prev) => !prev), className: "flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base rounded-lg border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base shadow-2xs transition-colors", title: isFullScreen ? "Exit Fullscreen" : "Expand to Full Page Modal", children: [(0, jsx_runtime_1.jsx)(icons_1.ArrowsPointingOut, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: isFullScreen ? "Exit Fullscreen" : "Full Page" })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => window.open(`/app/categories/${category.id}`, "_blank"), className: "flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base rounded-lg border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base shadow-2xs transition-colors", title: "Open full category details page in a new tab", children: [(0, jsx_runtime_1.jsx)("span", { children: "Open Full Page" }), (0, jsx_runtime_1.jsx)(icons_1.ArrowUpRightMicro, { className: "w-3.5 h-3.5" })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 mt-3 border-b border-ui-border-base/50 -mb-3 pb-1", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveTab("general"), className: `px-3 py-1.5 text-xs font-medium rounded-t-lg transition-colors border-b-2 -mb-[5px] ${activeTab === "general"
364
- ? "border-ui-border-interactive text-ui-fg-base bg-ui-bg-subtle/50"
365
- : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"}`, children: "General & Ranking" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveTab("products"), className: `px-3 py-1.5 text-xs font-medium rounded-t-lg transition-colors border-b-2 -mb-[5px] ${activeTab === "products"
366
- ? "border-ui-border-interactive text-ui-fg-base bg-ui-bg-subtle/50"
367
- : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"}`, children: "Products Management" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveTab("channels"), className: `px-3 py-1.5 text-xs font-medium rounded-t-lg transition-colors border-b-2 -mb-[5px] ${activeTab === "channels"
368
- ? "border-ui-border-interactive text-ui-fg-base bg-ui-bg-subtle/50"
369
- : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"}`, children: "Storefronts & Media" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => setActiveTab("youtube"), className: `px-3 py-1.5 text-xs font-medium rounded-t-lg transition-colors border-b-2 -mb-[5px] ${activeTab === "youtube"
370
- ? "border-ui-border-interactive text-ui-fg-base bg-ui-bg-subtle/50"
371
- : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"}`, children: ["YouTube Videos (", youtubeUrls.length, ")"] })] })] }), (0, jsx_runtime_1.jsxs)(ui_1.Drawer.Body, { className: "flex flex-col gap-4 overflow-y-auto px-6 py-4 flex-1", children: [activeTab === "general" && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5", children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { htmlFor: "edit-name", className: "text-xs font-semibold", children: "Category Name *" }), (0, jsx_runtime_1.jsx)(ui_1.Input, { id: "edit-name", size: "small", value: name, onChange: (e) => setName(e.target.value) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5", children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { htmlFor: "edit-handle", className: "text-xs font-semibold", children: "Handle (URL slug)" }), (0, jsx_runtime_1.jsx)(ui_1.Input, { id: "edit-handle", size: "small", value: handle, onChange: (e) => setHandle(e.target.value) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5 p-3 rounded-lg bg-ui-bg-subtle border border-ui-border-base", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { htmlFor: "edit-rank", className: "text-xs font-semibold", children: "Storefront Ranking (Sort Order)" }), (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] font-mono text-ui-fg-muted", children: "Controls Storefront 2 & Monfil" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(ui_1.Input, { id: "edit-rank", type: "number", size: "small", value: rank, onChange: (e) => setRank(Number(e.target.value)), className: "w-24" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setRank((r) => Math.max(0, r - 1)), className: "px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors", title: "Move Earlier in Storefront", children: "\u2191 Earlier" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setRank((r) => r + 1), className: "px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors", title: "Move Later in Storefront", children: "\u2193 Later" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setRank(0), className: "px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors", title: "Set as First Category", children: "Top (#0)" })] })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-[11px] text-ui-fg-muted leading-relaxed", children: "Categories are sorted by rank ASC (0, 1, 2...). Lower numbers appear first on your storefront navigation menu." })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5", children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { htmlFor: "edit-desc", className: "text-xs font-semibold", children: "Description" }), (0, jsx_runtime_1.jsx)(ui_1.Textarea, { id: "edit-desc", value: description, onChange: (e) => setDescription(e.target.value), rows: 3 })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between pt-2 border-t border-ui-border-base", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { className: "text-xs font-semibold", children: "Active Status" }), (0, jsx_runtime_1.jsx)(ui_1.Text, { size: "xsmall", className: "text-ui-fg-muted", children: "Visible to shoppers on enabled storefronts" })] }), (0, jsx_runtime_1.jsx)(ui_1.Switch, { checked: isActive, onCheckedChange: setIsActive })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { className: "text-xs font-semibold", children: "Internal Only" }), (0, jsx_runtime_1.jsx)(ui_1.Text, { size: "xsmall", className: "text-ui-fg-muted", children: "Hidden from public storefront navigation" })] }), (0, jsx_runtime_1.jsx)(ui_1.Switch, { checked: isInternal, onCheckedChange: setIsInternal })] }), (0, jsx_runtime_1.jsx)("div", { className: "pt-3 border-t border-ui-border-base", children: (0, jsx_runtime_1.jsxs)(ui_1.Button, { type: "button", variant: "danger", size: "small", onClick: handleDelete, isLoading: isDeleting, className: "w-full flex items-center justify-center gap-1.5", children: [(0, jsx_runtime_1.jsx)(icons_1.Trash, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: "Delete Category" })] }) })] })), activeTab === "products" && ((0, jsx_runtime_1.jsx)("div", { className: "flex flex-col gap-3", children: (0, jsx_runtime_1.jsx)(category_products_manager_1.CategoryProductsManager, { categoryId: category.id, categoryName: category.name, initialMetadata: currentMetadata, onMetadataUpdate: (meta) => {
433
+ : "max-w-5xl xl:max-w-6xl w-[92vw]"}`, children: (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col h-full", children: [(0, jsx_runtime_1.jsxs)(ui_1.Drawer.Header, { className: "border-b border-ui-border-base pb-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between w-full", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(ui_1.Drawer.Title, { className: "text-base font-semibold text-ui-fg-base", children: category.name }), (0, jsx_runtime_1.jsxs)(ui_1.Drawer.Description, { className: "text-xs text-ui-fg-muted mt-0.5", children: ["Tier ", category.tier, " \u2022 ID: ", category.id] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => setIsFullScreen((prev) => !prev), className: "flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base rounded-lg border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base shadow-2xs transition-colors cursor-pointer", title: isFullScreen ? "Exit Fullscreen" : "Expand to Full Page Modal", children: [(0, jsx_runtime_1.jsx)(icons_1.ArrowsPointingOut, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: isFullScreen ? "Exit Fullscreen" : "Full Page" })] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => window.open(`/app/categories/${category.id}`, "_blank"), className: "flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base rounded-lg border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base shadow-2xs transition-colors cursor-pointer", title: "Open full category details page in a new tab", children: [(0, jsx_runtime_1.jsx)("span", { children: "Open Full Page" }), (0, jsx_runtime_1.jsx)(icons_1.ArrowUpRightMicro, { className: "w-3.5 h-3.5" })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1 mt-3 border-b border-ui-border-base -mb-3", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveTab("general"), className: `px-3 py-1.5 text-xs font-medium border-b-2 transition-colors cursor-pointer ${activeTab === "general"
434
+ ? "border-blue-600 text-blue-600 font-semibold"
435
+ : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"}`, children: "General & Ranking" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveTab("products"), className: `px-3 py-1.5 text-xs font-medium border-b-2 transition-colors cursor-pointer ${activeTab === "products"
436
+ ? "border-blue-600 text-blue-600 font-semibold"
437
+ : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"}`, children: "Products Management" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setActiveTab("channels"), className: `px-3 py-1.5 text-xs font-medium border-b-2 transition-colors cursor-pointer ${activeTab === "channels"
438
+ ? "border-blue-600 text-blue-600 font-semibold"
439
+ : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"}`, children: "Storefronts & Media" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: () => setActiveTab("youtube"), className: `flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border-b-2 transition-colors cursor-pointer ${activeTab === "youtube"
440
+ ? "border-blue-600 text-blue-600 font-semibold"
441
+ : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"}`, children: [(0, jsx_runtime_1.jsx)("span", { children: "YouTube Videos" }), youtubeUrls.length > 0 && ((0, jsx_runtime_1.jsx)("span", { className: "px-1.5 py-0.2 rounded-full text-[10px] font-bold bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200", children: youtubeUrls.length }))] })] })] }), (0, jsx_runtime_1.jsxs)(ui_1.Drawer.Body, { className: "overflow-y-auto flex-1 p-6", children: [activeTab === "general" && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5", children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { htmlFor: "edit-name", className: "text-xs font-semibold", children: "Category Name *" }), (0, jsx_runtime_1.jsx)(ui_1.Input, { id: "edit-name", value: name, onChange: (e) => setName(e.target.value), required: true })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5", children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { htmlFor: "edit-handle", className: "text-xs font-semibold", children: "Handle (URL Slug)" }), (0, jsx_runtime_1.jsx)(ui_1.Input, { id: "edit-handle", value: handle, onChange: (e) => setHandle(e.target.value), placeholder: "e.g. rayon-embroidery-thread" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5 p-3 rounded-xl border border-ui-border-base bg-ui-bg-subtle/50", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { htmlFor: "edit-rank", className: "text-xs font-semibold", children: "Storefront Ranking (Sort Order)" }), (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] font-mono text-ui-fg-muted", children: "Controls Storefront 2 & Monfil" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(ui_1.Input, { id: "edit-rank", type: "number", size: "small", value: rank, onChange: (e) => setRank(Number(e.target.value)), className: "w-24" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-1", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setRank((r) => Math.max(0, r - 1)), className: "px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors cursor-pointer", title: "Move Earlier in Storefront", children: "\u2191 Earlier" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setRank((r) => r + 1), className: "px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors cursor-pointer", title: "Move Later in Storefront", children: "\u2193 Later" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => setRank(0), className: "px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors cursor-pointer", title: "Set as First Category", children: "Top (#0)" })] })] }), (0, jsx_runtime_1.jsx)("p", { className: "text-[11px] text-ui-fg-muted leading-relaxed", children: "Categories are sorted by rank ASC (0, 1, 2...). Lower numbers appear first on your storefront navigation menu." })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5", children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { htmlFor: "edit-desc", className: "text-xs font-semibold", children: "Description" }), (0, jsx_runtime_1.jsx)(ui_1.Textarea, { id: "edit-desc", value: description, onChange: (e) => setDescription(e.target.value), rows: 3 })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between pt-2 border-t border-ui-border-base", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { className: "text-xs font-semibold", children: "Active Status" }), (0, jsx_runtime_1.jsx)(ui_1.Text, { size: "xsmall", className: "text-ui-fg-muted", children: "Visible to shoppers on enabled storefronts" })] }), (0, jsx_runtime_1.jsx)(ui_1.Switch, { checked: isActive, onCheckedChange: setIsActive })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center justify-between", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { className: "text-xs font-semibold", children: "Internal Only" }), (0, jsx_runtime_1.jsx)(ui_1.Text, { size: "xsmall", className: "text-ui-fg-muted", children: "Hidden from public storefront navigation" })] }), (0, jsx_runtime_1.jsx)(ui_1.Switch, { checked: isInternal, onCheckedChange: setIsInternal })] }), (0, jsx_runtime_1.jsx)("div", { className: "pt-3 border-t border-ui-border-base", children: (0, jsx_runtime_1.jsxs)(ui_1.Button, { type: "button", variant: "danger", size: "small", onClick: handleDelete, isLoading: isDeleting, className: "w-full flex items-center justify-center gap-1.5", children: [(0, jsx_runtime_1.jsx)(icons_1.Trash, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: "Delete Category" })] }) })] })), activeTab === "products" && ((0, jsx_runtime_1.jsx)("div", { className: "flex flex-col gap-3", children: (0, jsx_runtime_1.jsx)(category_products_manager_1.CategoryProductsManager, { categoryId: category.id, categoryName: category.name, initialMetadata: currentMetadata, onMetadataUpdate: (meta) => {
372
442
  setCurrentMetadata(meta);
373
443
  } }) })), activeTab === "channels" && ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2", children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { className: "text-xs font-semibold", children: "Storefront Visibility" }), (0, jsx_runtime_1.jsx)(ui_1.Text, { size: "xsmall", className: "text-ui-fg-muted", children: "Designate which storefront sales channels display this category." }), (0, jsx_runtime_1.jsx)("div", { className: "flex flex-col gap-2 mt-1", children: salesChannels.map((channel) => {
374
444
  const isChecked = selectedChannels.includes(channel.id);
@@ -395,16 +465,16 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
395
465
  e.preventDefault();
396
466
  e.stopPropagation();
397
467
  handleSetThumbnail(img);
398
- }, className: "w-full py-0.5 bg-white text-neutral-900 rounded text-[9px] font-semibold hover:bg-neutral-100 transition-colors shadow-xs text-center", children: "Set Thumbnail" })), !isBanner && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: (e) => {
468
+ }, className: "w-full py-0.5 bg-white text-neutral-900 rounded text-[9px] font-semibold hover:bg-neutral-100 transition-colors shadow-xs text-center cursor-pointer", children: "Set Thumbnail" })), !isBanner && ((0, jsx_runtime_1.jsx)("button", { type: "button", onClick: (e) => {
399
469
  e.preventDefault();
400
470
  e.stopPropagation();
401
471
  handleSetBanner(img);
402
- }, className: "w-full py-0.5 bg-emerald-500 text-white rounded text-[9px] font-semibold hover:bg-emerald-600 transition-colors shadow-xs text-center", children: "Set as Banner" })), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: (e) => {
472
+ }, className: "w-full py-0.5 bg-emerald-600 text-white rounded text-[9px] font-semibold hover:bg-emerald-700 transition-colors shadow-xs text-center cursor-pointer", children: "Set as Banner" })), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: (e) => {
403
473
  e.preventDefault();
404
474
  e.stopPropagation();
405
475
  handleDeleteImage(img);
406
- }, className: "p-1 bg-rose-600 text-white rounded hover:bg-rose-700 transition-colors", title: "Remove image", children: (0, jsx_runtime_1.jsx)(icons_1.Trash, { className: "w-3.5 h-3.5" }) })] })] }, img.id || img.url || idx));
407
- }) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5 mt-1 pt-2 border-t border-ui-border-base/50", children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { htmlFor: "manual-image-url", className: "text-[11px] text-ui-fg-muted font-medium", children: "Or paste an external image URL to add to gallery" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(ui_1.Input, { id: "manual-image-url", size: "small", value: manualImageUrl, onChange: (e) => setManualImageUrl(e.target.value), placeholder: "https://example.com/banner-or-thumbnail.jpg", className: "flex-1 text-xs", onKeyDown: (e) => {
476
+ }, className: "w-full py-0.5 bg-rose-600 text-white rounded text-[9px] font-semibold hover:bg-rose-700 transition-colors shadow-xs text-center cursor-pointer", children: "Delete" })] })] }, img.id || img.url || idx));
477
+ }) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5 mt-2 p-3 rounded-xl border border-ui-border-base bg-ui-bg-subtle/30", children: [(0, jsx_runtime_1.jsx)(ui_1.Label, { className: "text-xs font-semibold text-ui-fg-base", children: "External Image URL (CDN / Manual)" }), (0, jsx_runtime_1.jsx)("p", { className: "text-[11px] text-ui-fg-muted", children: "Or paste a direct image URL (S3, Cloudinary, Imgur, Supabase) to link without uploading." }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2 mt-1", children: [(0, jsx_runtime_1.jsx)(ui_1.Input, { size: "small", type: "url", placeholder: "https://example.com/category-image.jpg", value: manualImageUrl, onChange: (e) => setManualImageUrl(e.target.value), className: "text-xs flex-1", onKeyDown: (e) => {
408
478
  if (e.key === "Enter") {
409
479
  e.preventDefault();
410
480
  handleAddManualImage();
@@ -417,7 +487,7 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
417
487
  } }), (0, jsx_runtime_1.jsxs)(ui_1.Button, { size: "small", type: "button", variant: "secondary", onClick: handleAddYoutube, className: "flex items-center gap-1 text-xs flex-shrink-0", children: [(0, jsx_runtime_1.jsx)(icons_1.Plus, { className: "w-3.5 h-3.5" }), (0, jsx_runtime_1.jsx)("span", { children: "Add Video" })] })] }), youtubeUrls.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "p-8 text-center text-xs text-ui-fg-muted border border-dashed border-ui-border-base rounded-xl", children: "No demonstration videos attached yet." })) : ((0, jsx_runtime_1.jsx)("div", { className: "grid grid-cols-2 gap-3", children: youtubeUrls.map((url) => {
418
488
  const id = extractYouTubeVideoId(url);
419
489
  const thumb = id ? `https://img.youtube.com/vi/${id}/hqdefault.jpg` : null;
420
- return ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col border border-ui-border-base rounded-xl overflow-hidden bg-ui-bg-subtle/40 shadow-xs", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative aspect-video bg-black/80 overflow-hidden flex items-center justify-center", children: [thumb ? ((0, jsx_runtime_1.jsx)("img", { src: thumb, alt: "YouTube Preview", className: "w-full h-full object-cover" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-xs text-white", children: "Video" })), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => handleRemoveYoutube(url), className: "absolute top-1.5 right-1.5 p-1 bg-black/70 hover:bg-rose-600 text-white rounded-full transition-colors", title: "Remove video", children: (0, jsx_runtime_1.jsx)(icons_1.Trash, { className: "w-3 h-3" }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "p-2", children: (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] text-ui-fg-muted truncate block font-mono", title: url, children: url }) })] }, url));
421
- }) }))] }))] }), (0, jsx_runtime_1.jsxs)(ui_1.Drawer.Footer, { className: "flex justify-end gap-2 px-6 py-4 border-t border-ui-border-base", children: [(0, jsx_runtime_1.jsx)(ui_1.Button, { type: "button", variant: "secondary", size: "small", onClick: () => onOpenChange(false), disabled: isSubmitting || isDeleting, children: "Close" }), (0, jsx_runtime_1.jsx)(ui_1.Button, { type: "submit", size: "small", isLoading: isSubmitting, disabled: isSubmitting || !name.trim() || isDeleting, children: "Save Category" })] })] }) }) }));
490
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col border border-ui-border-base rounded-xl overflow-hidden bg-ui-bg-subtle/40 shadow-xs", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative aspect-video bg-black/80 overflow-hidden flex items-center justify-center", children: [thumb ? ((0, jsx_runtime_1.jsx)("img", { src: thumb, alt: "YouTube Preview", className: "w-full h-full object-cover" })) : ((0, jsx_runtime_1.jsx)("span", { className: "text-xs text-white", children: "Video" })), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: () => handleRemoveYoutube(url), className: "absolute top-1.5 right-1.5 p-1 bg-black/70 hover:bg-rose-600 text-white rounded-full transition-colors cursor-pointer", title: "Remove video", children: (0, jsx_runtime_1.jsx)(icons_1.Trash, { className: "w-3 h-3" }) })] }), (0, jsx_runtime_1.jsx)("div", { className: "p-2", children: (0, jsx_runtime_1.jsx)("span", { className: "text-[10px] text-ui-fg-muted truncate block font-mono", title: url, children: url }) })] }, url));
491
+ }) }))] }))] }), (0, jsx_runtime_1.jsxs)(ui_1.Drawer.Footer, { className: "flex justify-end gap-2 px-6 py-4 border-t border-ui-border-base", children: [(0, jsx_runtime_1.jsx)(ui_1.Button, { type: "button", variant: "secondary", size: "small", onClick: () => onOpenChange(false), disabled: isSubmitting || isDeleting, children: "Close" }), (0, jsx_runtime_1.jsx)(ui_1.Button, { type: "button", size: "small", onClick: () => handleSubmit(), isLoading: isSubmitting, disabled: isSubmitting || !name.trim() || isDeleting, children: "Save Category" })] })] }) }) }));
422
492
  };
423
493
  exports.EditCategoryDrawer = EditCategoryDrawer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hectasquare/medusa-plugin-category-tree",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "AAA Interactive Category Tree & Mind Map for Medusa v2 Admin with drag-and-drop reparenting, tier indicators, superscript ranks, multi-storefront filtering, and NotebookLM-style subtree collapse.",
5
5
  "author": "HectaSquare Team",
6
6
  "license": "Apache-2.0",
@@ -10,6 +10,7 @@ import {
10
10
  MagnifyingGlass,
11
11
  CheckMini,
12
12
  Sparkles,
13
+ ArrowUpRightMicro,
13
14
  } from "@medusajs/icons"
14
15
  import { sdk } from "../../lib/sdk"
15
16
 
@@ -166,7 +167,7 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
166
167
  await persistProductConfig(currentOrder, next)
167
168
  }
168
169
 
169
- // Remove products from category
170
+ // Remove products from category using official Medusa v2 JS SDK method
170
171
  const handleRemoveProducts = async (idsToRemove: string[]) => {
171
172
  if (idsToRemove.length === 0) return
172
173
  const msg =
@@ -177,9 +178,8 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
177
178
 
178
179
  setIsRemoving(true)
179
180
  try {
180
- await sdk.client.fetch(`/admin/product-categories/${categoryId}/products`, {
181
- method: "POST",
182
- body: { remove: idsToRemove },
181
+ await sdk.admin.productCategory.updateProducts(categoryId, {
182
+ remove: idsToRemove,
183
183
  })
184
184
  toast.success(`Removed ${idsToRemove.length} product(s) from category`)
185
185
  setSelectedIds(new Set())
@@ -224,15 +224,15 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
224
224
  }
225
225
  }, [focusModalOpen, searchQuery, catalogPage, searchCatalog])
226
226
 
227
+ // Save added products using official Medusa v2 JS SDK method
227
228
  const handleSaveAddedProducts = async () => {
228
229
  const idsToAdd = Array.from(catalogSelectedIds)
229
230
  if (idsToAdd.length === 0) return
230
231
 
231
232
  setIsAdding(true)
232
233
  try {
233
- await sdk.client.fetch(`/admin/product-categories/${categoryId}/products`, {
234
- method: "POST",
235
- body: { add: idsToAdd },
234
+ await sdk.admin.productCategory.updateProducts(categoryId, {
235
+ add: idsToAdd,
236
236
  })
237
237
  toast.success(`Added ${idsToAdd.length} product(s) to ${categoryName}`)
238
238
  setFocusModalOpen(false)
@@ -267,7 +267,11 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
267
267
  <div className="flex items-center gap-1.5 p-1 rounded-lg bg-ui-bg-subtle border border-ui-border-base">
268
268
  <button
269
269
  type="button"
270
- onClick={() => setActiveView("all")}
270
+ onClick={(e) => {
271
+ e.preventDefault()
272
+ e.stopPropagation()
273
+ setActiveView("all")
274
+ }}
271
275
  className={`px-2.5 py-1 text-xs font-medium rounded-md transition-colors ${
272
276
  activeView === "all"
273
277
  ? "bg-ui-bg-base text-ui-fg-base shadow-2xs font-semibold"
@@ -278,7 +282,11 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
278
282
  </button>
279
283
  <button
280
284
  type="button"
281
- onClick={() => setActiveView("top_sellers")}
285
+ onClick={(e) => {
286
+ e.preventDefault()
287
+ e.stopPropagation()
288
+ setActiveView("top_sellers")
289
+ }}
282
290
  className={`flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md transition-colors ${
283
291
  activeView === "top_sellers"
284
292
  ? "bg-amber-500/10 text-amber-700 dark:text-amber-400 font-semibold"
@@ -293,10 +301,15 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
293
301
  <div className="flex items-center gap-2">
294
302
  {selectedIds.size > 0 && (
295
303
  <Button
304
+ type="button"
296
305
  size="small"
297
306
  variant="danger"
298
307
  disabled={isRemoving}
299
- onClick={() => handleRemoveProducts(Array.from(selectedIds))}
308
+ onClick={(e) => {
309
+ e.preventDefault()
310
+ e.stopPropagation()
311
+ handleRemoveProducts(Array.from(selectedIds))
312
+ }}
300
313
  className="flex items-center gap-1 text-xs"
301
314
  >
302
315
  <Trash className="w-3.5 h-3.5" />
@@ -305,9 +318,12 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
305
318
  )}
306
319
 
307
320
  <Button
321
+ type="button"
308
322
  size="small"
309
323
  variant="secondary"
310
- onClick={() => {
324
+ onClick={(e) => {
325
+ e.preventDefault()
326
+ e.stopPropagation()
311
327
  setCatalogSelectedIds(new Set())
312
328
  setSearchQuery("")
313
329
  setCatalogPage(0)
@@ -336,9 +352,17 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
336
352
  </p>
337
353
  {activeView === "all" && (
338
354
  <Button
355
+ type="button"
339
356
  size="small"
340
357
  variant="secondary"
341
- onClick={() => setFocusModalOpen(true)}
358
+ onClick={(e) => {
359
+ e.preventDefault()
360
+ e.stopPropagation()
361
+ setCatalogSelectedIds(new Set())
362
+ setSearchQuery("")
363
+ setCatalogPage(0)
364
+ setFocusModalOpen(true)
365
+ }}
342
366
  className="flex items-center gap-1 text-xs"
343
367
  >
344
368
  <Plus className="w-3.5 h-3.5" />
@@ -435,20 +459,33 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
435
459
  </div>
436
460
  </td>
437
461
 
438
- {/* Product Thumbnail & Title */}
462
+ {/* Product Thumbnail & Title (Clickable link to Medusa Admin product page) */}
439
463
  <td className="py-2.5 px-3">
440
464
  <div className="flex items-center gap-2.5 min-w-0">
441
- <div className="w-9 h-9 rounded-md border border-ui-border-base overflow-hidden bg-ui-bg-subtle flex-shrink-0 flex items-center justify-center">
465
+ <a
466
+ href={`/app/products/${p.id}`}
467
+ target="_blank"
468
+ rel="noopener noreferrer"
469
+ className="w-9 h-9 rounded-md border border-ui-border-base overflow-hidden bg-ui-bg-subtle flex-shrink-0 flex items-center justify-center hover:opacity-80 transition-opacity"
470
+ title={`Open ${p.title} in new tab`}
471
+ >
442
472
  {p.thumbnail ? (
443
473
  <img src={p.thumbnail} alt={p.title} className="w-full h-full object-cover" />
444
474
  ) : (
445
475
  <span className="text-[9px] text-ui-fg-muted">IMG</span>
446
476
  )}
447
- </div>
477
+ </a>
448
478
  <div className="flex flex-col min-w-0">
449
- <span className="font-semibold text-ui-fg-base truncate leading-snug" title={p.title}>
450
- {p.title}
451
- </span>
479
+ <a
480
+ href={`/app/products/${p.id}`}
481
+ target="_blank"
482
+ rel="noopener noreferrer"
483
+ className="font-semibold text-ui-fg-base hover:text-ui-fg-interactive truncate leading-snug hover:underline flex items-center gap-1 group/pname"
484
+ title={`Open ${p.title} in new tab`}
485
+ >
486
+ <span className="truncate">{p.title}</span>
487
+ <ArrowUpRightMicro className="w-3.5 h-3.5 text-ui-fg-muted group-hover/pname:text-ui-fg-interactive transition-colors shrink-0" />
488
+ </a>
452
489
  </div>
453
490
  </div>
454
491
  </td>
@@ -462,7 +499,7 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
462
499
  e.stopPropagation()
463
500
  handleToggleTopSeller(p.id, p.title)
464
501
  }}
465
- className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-semibold border transition-all ${
502
+ className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-semibold border transition-all cursor-pointer ${
466
503
  isTopSeller
467
504
  ? "bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-300 dark:border-amber-700 shadow-2xs"
468
505
  : "bg-ui-bg-subtle text-ui-fg-muted border-ui-border-base hover:text-amber-600 hover:border-amber-300"
@@ -470,7 +507,7 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
470
507
  title={
471
508
  isTopSeller
472
509
  ? "Click to remove from Top Commercial Sellers"
473
- : "Click to mark as Top Commercial Seller (Reflected in Storefront 2)"
510
+ : "Click to mark as Top Commercial Seller (Reflected on Storefront 2)"
474
511
  }
475
512
  >
476
513
  <span>{isTopSeller ? "★" : "☆"}</span>
@@ -507,7 +544,7 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
507
544
  e.stopPropagation()
508
545
  handleRemoveProducts([p.id])
509
546
  }}
510
- className="p-1 text-ui-fg-muted hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/50 rounded transition-colors"
547
+ className="p-1 text-ui-fg-muted hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/50 rounded transition-colors cursor-pointer"
511
548
  >
512
549
  <Trash className="w-3.5 h-3.5" />
513
550
  </button>
@@ -530,8 +567,12 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
530
567
  <div className="flex items-center gap-3">
531
568
  <button
532
569
  type="button"
533
- onClick={() => setFocusModalOpen(false)}
534
- className="p-1.5 rounded-lg text-ui-fg-muted hover:text-ui-fg-base hover:bg-ui-bg-subtle transition-colors"
570
+ onClick={(e) => {
571
+ e.preventDefault()
572
+ e.stopPropagation()
573
+ setFocusModalOpen(false)
574
+ }}
575
+ className="p-1.5 rounded-lg text-ui-fg-muted hover:text-ui-fg-base hover:bg-ui-bg-subtle transition-colors cursor-pointer"
535
576
  title="Close (ESC)"
536
577
  >
537
578
  <span className="text-sm font-bold">✕</span>
@@ -728,16 +769,24 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
728
769
  <button
729
770
  type="button"
730
771
  disabled={catalogPage === 0}
731
- onClick={() => setCatalogPage((p) => Math.max(0, p - 1))}
732
- className="px-2.5 py-1 text-xs rounded border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base disabled:opacity-40"
772
+ onClick={(e) => {
773
+ e.preventDefault()
774
+ e.stopPropagation()
775
+ setCatalogPage((p) => Math.max(0, p - 1))
776
+ }}
777
+ className="px-2.5 py-1 text-xs rounded border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base disabled:opacity-40 cursor-pointer"
733
778
  >
734
779
  Prev
735
780
  </button>
736
781
  <button
737
782
  type="button"
738
783
  disabled={(catalogPage + 1) * PAGE_SIZE >= catalogTotalCount}
739
- onClick={() => setCatalogPage((p) => p + 1)}
740
- className="px-2.5 py-1 text-xs rounded border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base disabled:opacity-40"
784
+ onClick={(e) => {
785
+ e.preventDefault()
786
+ e.stopPropagation()
787
+ setCatalogPage((p) => p + 1)
788
+ }}
789
+ className="px-2.5 py-1 text-xs rounded border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base disabled:opacity-40 cursor-pointer"
741
790
  >
742
791
  Next
743
792
  </button>
@@ -745,17 +794,27 @@ export const CategoryProductsManager: React.FC<CategoryProductsManagerProps> = (
745
794
  <div className="h-4 w-[1px] bg-ui-border-base mx-1" />
746
795
 
747
796
  <Button
797
+ type="button"
748
798
  size="small"
749
799
  variant="secondary"
750
- onClick={() => setFocusModalOpen(false)}
800
+ onClick={(e) => {
801
+ e.preventDefault()
802
+ e.stopPropagation()
803
+ setFocusModalOpen(false)
804
+ }}
751
805
  >
752
806
  Cancel
753
807
  </Button>
754
808
  <Button
809
+ type="button"
755
810
  size="small"
756
811
  variant="primary"
757
812
  disabled={catalogSelectedIds.size === 0 || isAdding}
758
- onClick={handleSaveAddedProducts}
813
+ onClick={(e) => {
814
+ e.preventDefault()
815
+ e.stopPropagation()
816
+ handleSaveAddedProducts()
817
+ }}
759
818
  className="flex items-center gap-1 text-xs min-w-[70px] justify-center"
760
819
  >
761
820
  <CheckMini className="w-3.5 h-3.5" />
@@ -122,8 +122,13 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
122
122
  // Endpoint fallback
123
123
  }
124
124
 
125
- // Metadata fallback: if metadata.images array exists
126
- if (metadata?.images && Array.isArray(metadata.images) && metadata.images.length > 0) {
125
+ // Explicit metadata.images check: if metadata.images array exists (even if empty)
126
+ if (metadata && Array.isArray(metadata.images)) {
127
+ if (metadata.images.length === 0) {
128
+ setCategoryImages([])
129
+ setImageUrl("")
130
+ return
131
+ }
127
132
  setCategoryImages(metadata.images)
128
133
  const thumb = metadata.images.find((img: any) => img.type === "thumbnail") || metadata.images[0]
129
134
  if (thumb?.url) setImageUrl(thumb.url)
@@ -132,7 +137,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
132
137
 
133
138
  // Legacy fallback
134
139
  const list: Array<{ url: string; type: string; id?: string }> = []
135
- const thumb = currentImageUrl || metadata?.thumbnail || metadata?.image_url
140
+ const thumb = metadata?.thumbnail || metadata?.image_url || currentImageUrl
136
141
  if (thumb) {
137
142
  list.push({ url: thumb, type: "thumbnail" })
138
143
  }
@@ -267,8 +272,33 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
267
272
  )
268
273
  }
269
274
 
270
- // Delete category image
275
+ // Delete category image with immediate persistence & explicit nulls
271
276
  const handleDeleteImage = async (img: { id?: string; url: string }) => {
277
+ const remaining = categoryImages.filter(
278
+ (i) => i.url !== img.url && (img.id ? i.id !== img.id : true)
279
+ )
280
+ const nextThumb =
281
+ remaining.find((i) => i.type === "thumbnail") || remaining[0] || null
282
+ const nextBanner =
283
+ remaining.find((i) => i.type === "banner") ||
284
+ (remaining.length > 1 ? remaining.find((i) => i.url !== nextThumb?.url) : null) ||
285
+ null
286
+
287
+ // Update local state immediately
288
+ setCategoryImages(remaining)
289
+ setImageUrl(nextThumb ? nextThumb.url : "")
290
+
291
+ const updatedMeta: Record<string, any> = {
292
+ ...(currentMetadata || {}),
293
+ images: remaining,
294
+ thumbnail: nextThumb ? nextThumb.url : null,
295
+ image_url: nextThumb ? nextThumb.url : null,
296
+ banner: nextBanner ? nextBanner.url : null,
297
+ image: (nextBanner?.url || nextThumb?.url) || null,
298
+ }
299
+ setCurrentMetadata(updatedMeta)
300
+
301
+ // Call backend endpoint if DB relation exists
272
302
  if (img.id) {
273
303
  try {
274
304
  await sdk.client.fetch(`/admin/categories/${category.id}/images/batch`, {
@@ -278,20 +308,27 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
278
308
  ids: [img.id],
279
309
  },
280
310
  })
281
- } catch {
282
- // Fallback
311
+ } catch (err: any) {
312
+ console.warn("Could not delete from backend images endpoint:", err)
283
313
  }
284
314
  }
285
- setCategoryImages((prev) =>
286
- prev.filter((i) => i.url !== img.url && (img.id ? i.id !== img.id : true))
287
- )
288
- if (imageUrl === img.url) {
289
- const remaining = categoryImages.filter(
290
- (i) => i.url !== img.url && (img.id ? i.id !== img.id : true)
291
- )
292
- setImageUrl(remaining[0]?.url || "")
315
+
316
+ // Immediately persist metadata changes to Medusa
317
+ try {
318
+ await sdk.admin.productCategory.update(category.id, {
319
+ metadata: updatedMeta,
320
+ })
321
+ const updatedCat: CategoryTreeNode = {
322
+ ...category,
323
+ image_url: nextThumb ? nextThumb.url : "",
324
+ metadata: updatedMeta,
325
+ }
326
+ onSuccess(updatedCat)
327
+ toast.success("Image removed and changes saved")
328
+ } catch (err: any) {
329
+ console.warn("Could not immediately update category metadata on delete:", err)
330
+ toast.info("Image removed from gallery (remember to save)")
293
331
  }
294
- toast.info("Image removed")
295
332
  }
296
333
 
297
334
  if (!category) return null
@@ -304,7 +341,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
304
341
  )
305
342
  }
306
343
 
307
- const handleAddYoutube = () => {
344
+ const handleAddYoutube = async () => {
308
345
  const raw = newYoutubeInput.trim()
309
346
  if (!raw) return
310
347
  const id = extractYouTubeVideoId(raw)
@@ -317,17 +354,57 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
317
354
  toast.error("Video already added")
318
355
  return
319
356
  }
320
- setYoutubeUrls((prev) => [...prev, cleanUrl])
357
+ const nextUrls = [...youtubeUrls, cleanUrl]
358
+ setYoutubeUrls(nextUrls)
321
359
  setNewYoutubeInput("")
322
- toast.success("YouTube video added")
360
+
361
+ const updatedMeta = {
362
+ ...(currentMetadata || {}),
363
+ youtube_urls: nextUrls,
364
+ youtube_url: nextUrls[0] || null,
365
+ }
366
+ setCurrentMetadata(updatedMeta)
367
+
368
+ try {
369
+ await sdk.admin.productCategory.update(category.id, {
370
+ metadata: updatedMeta,
371
+ })
372
+ onSuccess({
373
+ ...category,
374
+ metadata: updatedMeta,
375
+ })
376
+ toast.success("YouTube video added and saved")
377
+ } catch {
378
+ toast.success("YouTube video added")
379
+ }
323
380
  }
324
381
 
325
- const handleRemoveYoutube = (urlToRemove: string) => {
326
- setYoutubeUrls((prev) => prev.filter((u) => u !== urlToRemove))
382
+ const handleRemoveYoutube = async (urlToRemove: string) => {
383
+ const nextUrls = youtubeUrls.filter((u) => u !== urlToRemove)
384
+ setYoutubeUrls(nextUrls)
385
+ const updatedMeta = {
386
+ ...(currentMetadata || {}),
387
+ youtube_urls: nextUrls,
388
+ youtube_url: nextUrls[0] || null,
389
+ }
390
+ setCurrentMetadata(updatedMeta)
391
+
392
+ try {
393
+ await sdk.admin.productCategory.update(category.id, {
394
+ metadata: updatedMeta,
395
+ })
396
+ onSuccess({
397
+ ...category,
398
+ metadata: updatedMeta,
399
+ })
400
+ toast.success("Video removed and changes saved")
401
+ } catch {
402
+ toast.info("Video removed (remember to save)")
403
+ }
327
404
  }
328
405
 
329
- const handleSubmit = async (e: React.FormEvent) => {
330
- e.preventDefault()
406
+ const handleSubmit = async (e?: React.FormEvent) => {
407
+ if (e && e.preventDefault) e.preventDefault()
331
408
  if (!name.trim()) {
332
409
  toast.error("Category name is required")
333
410
  return
@@ -337,26 +414,23 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
337
414
  try {
338
415
  const thumbImg =
339
416
  categoryImages.find((img) => img.type === "thumbnail") ||
340
- categoryImages[0]
417
+ categoryImages[0] ||
418
+ null
341
419
  const bannerImg =
342
420
  categoryImages.find((img) => img.type === "banner") ||
343
- categoryImages.find((img) => img.url !== thumbImg?.url)
421
+ (categoryImages.length > 1 ? categoryImages.find((img) => img.url !== thumbImg?.url) : null) ||
422
+ null
344
423
 
345
424
  const updatedMetadata: Record<string, any> = {
346
425
  ...(currentMetadata || {}),
347
426
  sales_channels: selectedChannels,
348
427
  youtube_urls: youtubeUrls,
428
+ youtube_url: youtubeUrls[0] || null,
349
429
  images: categoryImages,
350
- }
351
- if (thumbImg?.url) {
352
- updatedMetadata.thumbnail = thumbImg.url
353
- updatedMetadata.image_url = thumbImg.url
354
- }
355
- if (bannerImg?.url && bannerImg.url !== thumbImg?.url) {
356
- updatedMetadata.banner = bannerImg.url
357
- updatedMetadata.image = bannerImg.url
358
- } else if (thumbImg?.url) {
359
- updatedMetadata.image = thumbImg.url
430
+ thumbnail: thumbImg ? thumbImg.url : null,
431
+ image_url: thumbImg ? thumbImg.url : null,
432
+ banner: bannerImg ? bannerImg.url : null,
433
+ image: (bannerImg?.url || thumbImg?.url) || null,
360
434
  }
361
435
 
362
436
  await sdk.admin.productCategory.update(category.id, {
@@ -374,7 +448,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
374
448
  name: name.trim(),
375
449
  handle: handle.trim(),
376
450
  description: description.trim() || null,
377
- image_url: imageUrl.trim() || category.image_url,
451
+ image_url: thumbImg ? thumbImg.url : "",
378
452
  rank: Number(rank) || 0,
379
453
  sales_channels: selectedChannels,
380
454
  is_active: isActive,
@@ -419,7 +493,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
419
493
  : "max-w-5xl xl:max-w-6xl w-[92vw]"
420
494
  }`}
421
495
  >
422
- <form onSubmit={handleSubmit} className="flex flex-col h-full">
496
+ <div className="flex flex-col h-full">
423
497
  <Drawer.Header className="border-b border-ui-border-base pb-3">
424
498
  <div className="flex items-center justify-between w-full">
425
499
  <div>
@@ -436,7 +510,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
436
510
  <button
437
511
  type="button"
438
512
  onClick={() => setIsFullScreen((prev) => !prev)}
439
- className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base rounded-lg border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base shadow-2xs transition-colors"
513
+ className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base rounded-lg border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base shadow-2xs transition-colors cursor-pointer"
440
514
  title={isFullScreen ? "Exit Fullscreen" : "Expand to Full Page Modal"}
441
515
  >
442
516
  <ArrowsPointingOut className="w-3.5 h-3.5" />
@@ -447,7 +521,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
447
521
  <button
448
522
  type="button"
449
523
  onClick={() => window.open(`/app/categories/${category.id}`, "_blank")}
450
- className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base rounded-lg border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base shadow-2xs transition-colors"
524
+ className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-ui-fg-subtle hover:text-ui-fg-base rounded-lg border border-ui-border-base bg-ui-bg-subtle hover:bg-ui-bg-base shadow-2xs transition-colors cursor-pointer"
451
525
  title="Open full category details page in a new tab"
452
526
  >
453
527
  <span>Open Full Page</span>
@@ -456,59 +530,61 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
456
530
  </div>
457
531
  </div>
458
532
 
459
- {/* Tab Navigation Bar */}
460
- <div className="flex items-center gap-1 mt-3 border-b border-ui-border-base/50 -mb-3 pb-1">
533
+ {/* Sub-tab Navigation */}
534
+ <div className="flex items-center gap-1 mt-3 border-b border-ui-border-base -mb-3">
461
535
  <button
462
536
  type="button"
463
537
  onClick={() => setActiveTab("general")}
464
- className={`px-3 py-1.5 text-xs font-medium rounded-t-lg transition-colors border-b-2 -mb-[5px] ${
538
+ className={`px-3 py-1.5 text-xs font-medium border-b-2 transition-colors cursor-pointer ${
465
539
  activeTab === "general"
466
- ? "border-ui-border-interactive text-ui-fg-base bg-ui-bg-subtle/50"
540
+ ? "border-blue-600 text-blue-600 font-semibold"
467
541
  : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"
468
542
  }`}
469
543
  >
470
544
  General & Ranking
471
545
  </button>
472
-
473
546
  <button
474
547
  type="button"
475
548
  onClick={() => setActiveTab("products")}
476
- className={`px-3 py-1.5 text-xs font-medium rounded-t-lg transition-colors border-b-2 -mb-[5px] ${
549
+ className={`px-3 py-1.5 text-xs font-medium border-b-2 transition-colors cursor-pointer ${
477
550
  activeTab === "products"
478
- ? "border-ui-border-interactive text-ui-fg-base bg-ui-bg-subtle/50"
551
+ ? "border-blue-600 text-blue-600 font-semibold"
479
552
  : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"
480
553
  }`}
481
554
  >
482
555
  Products Management
483
556
  </button>
484
-
485
557
  <button
486
558
  type="button"
487
559
  onClick={() => setActiveTab("channels")}
488
- className={`px-3 py-1.5 text-xs font-medium rounded-t-lg transition-colors border-b-2 -mb-[5px] ${
560
+ className={`px-3 py-1.5 text-xs font-medium border-b-2 transition-colors cursor-pointer ${
489
561
  activeTab === "channels"
490
- ? "border-ui-border-interactive text-ui-fg-base bg-ui-bg-subtle/50"
562
+ ? "border-blue-600 text-blue-600 font-semibold"
491
563
  : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"
492
564
  }`}
493
565
  >
494
566
  Storefronts & Media
495
567
  </button>
496
-
497
568
  <button
498
569
  type="button"
499
570
  onClick={() => setActiveTab("youtube")}
500
- className={`px-3 py-1.5 text-xs font-medium rounded-t-lg transition-colors border-b-2 -mb-[5px] ${
571
+ className={`flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium border-b-2 transition-colors cursor-pointer ${
501
572
  activeTab === "youtube"
502
- ? "border-ui-border-interactive text-ui-fg-base bg-ui-bg-subtle/50"
573
+ ? "border-blue-600 text-blue-600 font-semibold"
503
574
  : "border-transparent text-ui-fg-muted hover:text-ui-fg-base"
504
575
  }`}
505
576
  >
506
- YouTube Videos ({youtubeUrls.length})
577
+ <span>YouTube Videos</span>
578
+ {youtubeUrls.length > 0 && (
579
+ <span className="px-1.5 py-0.2 rounded-full text-[10px] font-bold bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
580
+ {youtubeUrls.length}
581
+ </span>
582
+ )}
507
583
  </button>
508
584
  </div>
509
585
  </Drawer.Header>
510
586
 
511
- <Drawer.Body className="flex flex-col gap-4 overflow-y-auto px-6 py-4 flex-1">
587
+ <Drawer.Body className="overflow-y-auto flex-1 p-6">
512
588
  {/* TAB 1: GENERAL & RANKING */}
513
589
  {activeTab === "general" && (
514
590
  <div className="flex flex-col gap-4">
@@ -518,25 +594,26 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
518
594
  </Label>
519
595
  <Input
520
596
  id="edit-name"
521
- size="small"
522
597
  value={name}
523
598
  onChange={(e) => setName(e.target.value)}
599
+ required
524
600
  />
525
601
  </div>
526
602
 
527
603
  <div className="flex flex-col gap-1.5">
528
604
  <Label htmlFor="edit-handle" className="text-xs font-semibold">
529
- Handle (URL slug)
605
+ Handle (URL Slug)
530
606
  </Label>
531
607
  <Input
532
608
  id="edit-handle"
533
- size="small"
534
609
  value={handle}
535
610
  onChange={(e) => setHandle(e.target.value)}
611
+ placeholder="e.g. rayon-embroidery-thread"
536
612
  />
537
613
  </div>
538
614
 
539
- <div className="flex flex-col gap-1.5 p-3 rounded-lg bg-ui-bg-subtle border border-ui-border-base">
615
+ {/* Direct Rank Modifier */}
616
+ <div className="flex flex-col gap-1.5 p-3 rounded-xl border border-ui-border-base bg-ui-bg-subtle/50">
540
617
  <div className="flex items-center justify-between">
541
618
  <Label htmlFor="edit-rank" className="text-xs font-semibold">
542
619
  Storefront Ranking (Sort Order)
@@ -558,7 +635,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
558
635
  <button
559
636
  type="button"
560
637
  onClick={() => setRank((r) => Math.max(0, r - 1))}
561
- className="px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors"
638
+ className="px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors cursor-pointer"
562
639
  title="Move Earlier in Storefront"
563
640
  >
564
641
  ↑ Earlier
@@ -566,7 +643,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
566
643
  <button
567
644
  type="button"
568
645
  onClick={() => setRank((r) => r + 1)}
569
- className="px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors"
646
+ className="px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors cursor-pointer"
570
647
  title="Move Later in Storefront"
571
648
  >
572
649
  ↓ Later
@@ -574,7 +651,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
574
651
  <button
575
652
  type="button"
576
653
  onClick={() => setRank(0)}
577
- className="px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors"
654
+ className="px-2 py-1 text-xs font-medium rounded border border-ui-border-base bg-ui-bg-base hover:bg-ui-bg-subtle transition-colors cursor-pointer"
578
655
  title="Set as First Category"
579
656
  >
580
657
  Top (#0)
@@ -786,7 +863,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
786
863
  e.stopPropagation()
787
864
  handleSetThumbnail(img)
788
865
  }}
789
- className="w-full py-0.5 bg-white text-neutral-900 rounded text-[9px] font-semibold hover:bg-neutral-100 transition-colors shadow-xs text-center"
866
+ className="w-full py-0.5 bg-white text-neutral-900 rounded text-[9px] font-semibold hover:bg-neutral-100 transition-colors shadow-xs text-center cursor-pointer"
790
867
  >
791
868
  Set Thumbnail
792
869
  </button>
@@ -799,7 +876,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
799
876
  e.stopPropagation()
800
877
  handleSetBanner(img)
801
878
  }}
802
- className="w-full py-0.5 bg-emerald-500 text-white rounded text-[9px] font-semibold hover:bg-emerald-600 transition-colors shadow-xs text-center"
879
+ className="w-full py-0.5 bg-emerald-600 text-white rounded text-[9px] font-semibold hover:bg-emerald-700 transition-colors shadow-xs text-center cursor-pointer"
803
880
  >
804
881
  Set as Banner
805
882
  </button>
@@ -811,10 +888,9 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
811
888
  e.stopPropagation()
812
889
  handleDeleteImage(img)
813
890
  }}
814
- className="p-1 bg-rose-600 text-white rounded hover:bg-rose-700 transition-colors"
815
- title="Remove image"
891
+ className="w-full py-0.5 bg-rose-600 text-white rounded text-[9px] font-semibold hover:bg-rose-700 transition-colors shadow-xs text-center cursor-pointer"
816
892
  >
817
- <Trash className="w-3.5 h-3.5" />
893
+ Delete
818
894
  </button>
819
895
  </div>
820
896
  </div>
@@ -824,19 +900,22 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
824
900
  </div>
825
901
  )}
826
902
 
827
- {/* Manual URL input to add directly into the gallery */}
828
- <div className="flex flex-col gap-1.5 mt-1 pt-2 border-t border-ui-border-base/50">
829
- <Label htmlFor="manual-image-url" className="text-[11px] text-ui-fg-muted font-medium">
830
- Or paste an external image URL to add to gallery
903
+ {/* Manual / External Image URL Entry */}
904
+ <div className="flex flex-col gap-1.5 mt-2 p-3 rounded-xl border border-ui-border-base bg-ui-bg-subtle/30">
905
+ <Label className="text-xs font-semibold text-ui-fg-base">
906
+ External Image URL (CDN / Manual)
831
907
  </Label>
832
- <div className="flex items-center gap-2">
908
+ <p className="text-[11px] text-ui-fg-muted">
909
+ Or paste a direct image URL (S3, Cloudinary, Imgur, Supabase) to link without uploading.
910
+ </p>
911
+ <div className="flex items-center gap-2 mt-1">
833
912
  <Input
834
- id="manual-image-url"
835
913
  size="small"
914
+ type="url"
915
+ placeholder="https://example.com/category-image.jpg"
836
916
  value={manualImageUrl}
837
917
  onChange={(e) => setManualImageUrl(e.target.value)}
838
- placeholder="https://example.com/banner-or-thumbnail.jpg"
839
- className="flex-1 text-xs"
918
+ className="text-xs flex-1"
840
919
  onKeyDown={(e) => {
841
920
  if (e.key === "Enter") {
842
921
  e.preventDefault()
@@ -922,7 +1001,7 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
922
1001
  <button
923
1002
  type="button"
924
1003
  onClick={() => handleRemoveYoutube(url)}
925
- className="absolute top-1.5 right-1.5 p-1 bg-black/70 hover:bg-rose-600 text-white rounded-full transition-colors"
1004
+ className="absolute top-1.5 right-1.5 p-1 bg-black/70 hover:bg-rose-600 text-white rounded-full transition-colors cursor-pointer"
926
1005
  title="Remove video"
927
1006
  >
928
1007
  <Trash className="w-3 h-3" />
@@ -953,15 +1032,16 @@ export const EditCategoryDrawer: React.FC<EditCategoryDrawerProps> = ({
953
1032
  Close
954
1033
  </Button>
955
1034
  <Button
956
- type="submit"
1035
+ type="button"
957
1036
  size="small"
1037
+ onClick={() => handleSubmit()}
958
1038
  isLoading={isSubmitting}
959
1039
  disabled={isSubmitting || !name.trim() || isDeleting}
960
1040
  >
961
1041
  Save Category
962
1042
  </Button>
963
1043
  </Drawer.Footer>
964
- </form>
1044
+ </div>
965
1045
  </Drawer.Content>
966
1046
  </Drawer>
967
1047
  )