@hectasquare/medusa-plugin-category-tree 1.0.10 → 1.0.12
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.
- package/.medusa/server/src/admin/components/category-tree/category-products-manager.js +65 -22
- package/.medusa/server/src/admin/components/category-tree/edit-category-drawer.js +135 -53
- package/package.json +1 -1
- package/src/admin/components/category-tree/category-products-manager.tsx +96 -34
- package/src/admin/components/category-tree/edit-category-drawer.tsx +173 -83
|
@@ -126,12 +126,15 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
|
|
|
126
126
|
}
|
|
127
127
|
};
|
|
128
128
|
// Sibling product ranking handlers (Move Up / Move Down)
|
|
129
|
-
const handleMoveProduct = async (
|
|
130
|
-
const
|
|
129
|
+
const handleMoveProduct = async (productId, direction) => {
|
|
130
|
+
const currentIndex = products.findIndex((p) => p.id === productId);
|
|
131
|
+
if (currentIndex === -1)
|
|
132
|
+
return;
|
|
133
|
+
const targetIndex = direction === "up" ? currentIndex - 1 : currentIndex + 1;
|
|
131
134
|
if (targetIndex < 0 || targetIndex >= products.length)
|
|
132
135
|
return;
|
|
133
136
|
const reordered = [...products];
|
|
134
|
-
const [moved] = reordered.splice(
|
|
137
|
+
const [moved] = reordered.splice(currentIndex, 1);
|
|
135
138
|
reordered.splice(targetIndex, 0, moved);
|
|
136
139
|
setProducts(reordered);
|
|
137
140
|
const newOrder = reordered.map((p) => p.id);
|
|
@@ -155,7 +158,7 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
|
|
|
155
158
|
const currentOrder = products.map((p) => p.id);
|
|
156
159
|
await persistProductConfig(currentOrder, next);
|
|
157
160
|
};
|
|
158
|
-
// Remove products from category
|
|
161
|
+
// Remove products from category using official Medusa v2 JS SDK method
|
|
159
162
|
const handleRemoveProducts = async (idsToRemove) => {
|
|
160
163
|
if (idsToRemove.length === 0)
|
|
161
164
|
return;
|
|
@@ -166,9 +169,8 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
|
|
|
166
169
|
return;
|
|
167
170
|
setIsRemoving(true);
|
|
168
171
|
try {
|
|
169
|
-
await sdk_1.sdk.
|
|
170
|
-
|
|
171
|
-
body: { remove: idsToRemove },
|
|
172
|
+
await sdk_1.sdk.admin.productCategory.updateProducts(categoryId, {
|
|
173
|
+
remove: idsToRemove,
|
|
172
174
|
});
|
|
173
175
|
ui_1.toast.success(`Removed ${idsToRemove.length} product(s) from category`);
|
|
174
176
|
setSelectedIds(new Set());
|
|
@@ -210,15 +212,15 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
|
|
|
210
212
|
searchCatalog(searchQuery, catalogPage);
|
|
211
213
|
}
|
|
212
214
|
}, [focusModalOpen, searchQuery, catalogPage, searchCatalog]);
|
|
215
|
+
// Save added products using official Medusa v2 JS SDK method
|
|
213
216
|
const handleSaveAddedProducts = async () => {
|
|
214
217
|
const idsToAdd = Array.from(catalogSelectedIds);
|
|
215
218
|
if (idsToAdd.length === 0)
|
|
216
219
|
return;
|
|
217
220
|
setIsAdding(true);
|
|
218
221
|
try {
|
|
219
|
-
await sdk_1.sdk.
|
|
220
|
-
|
|
221
|
-
body: { add: idsToAdd },
|
|
222
|
+
await sdk_1.sdk.admin.productCategory.updateProducts(categoryId, {
|
|
223
|
+
add: idsToAdd,
|
|
222
224
|
});
|
|
223
225
|
ui_1.toast.success(`Added ${idsToAdd.length} product(s) to ${categoryName}`);
|
|
224
226
|
setFocusModalOpen(false);
|
|
@@ -243,18 +245,39 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
|
|
|
243
245
|
}
|
|
244
246
|
return products;
|
|
245
247
|
}, [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: () =>
|
|
248
|
+
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) => {
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
e.stopPropagation();
|
|
251
|
+
setActiveView("all");
|
|
252
|
+
}, className: `px-2.5 py-1 text-xs font-medium rounded-md transition-colors ${activeView === "all"
|
|
247
253
|
? "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: () =>
|
|
254
|
+
: "text-ui-fg-muted hover:text-ui-fg-base"}`, children: ["All Products (", products.length, ")"] }), (0, jsx_runtime_1.jsxs)("button", { type: "button", onClick: (e) => {
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
e.stopPropagation();
|
|
257
|
+
setActiveView("top_sellers");
|
|
258
|
+
}, className: `flex items-center gap-1 px-2.5 py-1 text-xs font-medium rounded-md transition-colors ${activeView === "top_sellers"
|
|
249
259
|
? "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: () =>
|
|
260
|
+
: "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) => {
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
e.stopPropagation();
|
|
263
|
+
handleRemoveProducts(Array.from(selectedIds));
|
|
264
|
+
}, 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) => {
|
|
265
|
+
e.preventDefault();
|
|
266
|
+
e.stopPropagation();
|
|
251
267
|
setCatalogSelectedIds(new Set());
|
|
252
268
|
setSearchQuery("");
|
|
253
269
|
setCatalogPage(0);
|
|
254
270
|
setFocusModalOpen(true);
|
|
255
271
|
}, 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
272
|
? "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, {
|
|
273
|
+
: "No products assigned to this category." }), activeView === "all" && ((0, jsx_runtime_1.jsxs)(ui_1.Button, { type: "button", size: "small", variant: "secondary", onClick: (e) => {
|
|
274
|
+
e.preventDefault();
|
|
275
|
+
e.stopPropagation();
|
|
276
|
+
setCatalogSelectedIds(new Set());
|
|
277
|
+
setSearchQuery("");
|
|
278
|
+
setCatalogPage(0);
|
|
279
|
+
setFocusModalOpen(true);
|
|
280
|
+
}, 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
281
|
if (selectedIds.size === displayedProducts.length)
|
|
259
282
|
setSelectedIds(new Set());
|
|
260
283
|
else
|
|
@@ -274,27 +297,31 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
|
|
|
274
297
|
}, 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-1 font-mono", children: [(0, jsx_runtime_1.jsxs)("span", { className: "text-xs font-bold text-ui-fg-subtle px-1.5 py-0.5 rounded bg-ui-bg-subtle border border-ui-border-base", children: ["#", index + 1] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", title: "Move Up", disabled: index === 0, onClick: (e) => {
|
|
275
298
|
e.preventDefault();
|
|
276
299
|
e.stopPropagation();
|
|
277
|
-
handleMoveProduct(
|
|
300
|
+
handleMoveProduct(p.id, "up");
|
|
278
301
|
}, className: "text-[9px] text-ui-fg-muted hover:text-ui-fg-base disabled:opacity-20 px-0.5 leading-none cursor-pointer", children: "\u25B2" }), (0, jsx_runtime_1.jsx)("button", { type: "button", title: "Move Down", disabled: index === displayedProducts.length - 1, onClick: (e) => {
|
|
279
302
|
e.preventDefault();
|
|
280
303
|
e.stopPropagation();
|
|
281
|
-
handleMoveProduct(
|
|
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)("
|
|
304
|
+
handleMoveProduct(p.id, "down");
|
|
305
|
+
}, 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
306
|
e.preventDefault();
|
|
284
307
|
e.stopPropagation();
|
|
285
308
|
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
|
|
309
|
+
}, 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
310
|
? "bg-amber-500/15 text-amber-700 dark:text-amber-400 border-amber-300 dark:border-amber-700 shadow-2xs"
|
|
288
311
|
: "bg-ui-bg-subtle text-ui-fg-muted border-ui-border-base hover:text-amber-600 hover:border-amber-300"}`, title: isTopSeller
|
|
289
312
|
? "Click to remove from Top Commercial Sellers"
|
|
290
|
-
: "Click to mark as Top Commercial Seller (Reflected
|
|
313
|
+
: "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
314
|
? "bg-emerald-50 text-emerald-700 border border-emerald-200 dark:bg-emerald-950/50 dark:text-emerald-300"
|
|
292
315
|
: "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
316
|
e.preventDefault();
|
|
294
317
|
e.stopPropagation();
|
|
295
318
|
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: (
|
|
319
|
+
}, 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));
|
|
320
|
+
}) })] })) }), 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) => {
|
|
321
|
+
e.preventDefault();
|
|
322
|
+
e.stopPropagation();
|
|
323
|
+
setFocusModalOpen(false);
|
|
324
|
+
}, 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
325
|
setSearchQuery(e.target.value);
|
|
299
326
|
setCatalogPage(0);
|
|
300
327
|
}, 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 +365,22 @@ const CategoryProductsManager = ({ categoryId, categoryName, initialMetadata, on
|
|
|
338
365
|
}, 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
366
|
? "bg-emerald-50 text-emerald-700 border border-emerald-200 dark:bg-emerald-950/50 dark:text-emerald-300"
|
|
340
367
|
: "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: () =>
|
|
368
|
+
})) })] }) }) }), (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) => {
|
|
369
|
+
e.preventDefault();
|
|
370
|
+
e.stopPropagation();
|
|
371
|
+
setCatalogPage((p) => Math.max(0, p - 1));
|
|
372
|
+
}, 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) => {
|
|
373
|
+
e.preventDefault();
|
|
374
|
+
e.stopPropagation();
|
|
375
|
+
setCatalogPage((p) => p + 1);
|
|
376
|
+
}, 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) => {
|
|
377
|
+
e.preventDefault();
|
|
378
|
+
e.stopPropagation();
|
|
379
|
+
setFocusModalOpen(false);
|
|
380
|
+
}, children: "Cancel" }), (0, jsx_runtime_1.jsxs)(ui_1.Button, { type: "button", size: "small", variant: "primary", disabled: catalogSelectedIds.size === 0 || isAdding, onClick: (e) => {
|
|
381
|
+
e.preventDefault();
|
|
382
|
+
e.stopPropagation();
|
|
383
|
+
handleSaveAddedProducts();
|
|
384
|
+
}, 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
385
|
};
|
|
343
386
|
exports.CategoryProductsManager = CategoryProductsManager;
|
|
@@ -12,17 +12,17 @@ function extractYouTubeVideoId(url) {
|
|
|
12
12
|
return null;
|
|
13
13
|
const trimmed = url.trim();
|
|
14
14
|
const patterns = [
|
|
15
|
-
/(?:youtube\.com\/shorts\/)([
|
|
16
|
-
/(?:youtube\.com\/watch\?.*v=)([
|
|
17
|
-
/(?:youtu\.be\/)([
|
|
18
|
-
/(?:youtube\.com\/embed\/)([
|
|
15
|
+
/(?:youtube\.com\/shorts\/)([a-zA-Z0-9_-]{11})/i,
|
|
16
|
+
/(?:youtube\.com\/watch\?.*v=)([a-zA-Z0-9_-]{11})/i,
|
|
17
|
+
/(?:youtu\.be\/)([a-zA-Z0-9_-]{11})/i,
|
|
18
|
+
/(?:youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/i,
|
|
19
19
|
];
|
|
20
20
|
for (const pattern of patterns) {
|
|
21
21
|
const match = trimmed.match(pattern);
|
|
22
22
|
if (match && match[1])
|
|
23
23
|
return match[1];
|
|
24
24
|
}
|
|
25
|
-
if (/^[a-zA-Z0-9_-]{11}$/.test(trimmed))
|
|
25
|
+
if (/^[a-zA-Z0-9_-]{11}$/.test(trimmed) && !/^[A-Za-z]{11}$/.test(trimmed))
|
|
26
26
|
return trimmed;
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
@@ -58,15 +58,21 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
58
58
|
setIsInternal(Boolean(category.is_internal));
|
|
59
59
|
setSelectedChannels(category.sales_channels || salesChannels.map((s) => s.id));
|
|
60
60
|
setCurrentMetadata(category.metadata || {});
|
|
61
|
-
// Parse YouTube URLs from metadata
|
|
61
|
+
// Parse YouTube URLs from metadata (including legacy fields)
|
|
62
62
|
const meta = category.metadata;
|
|
63
63
|
const urls = [];
|
|
64
64
|
if (meta?.youtube_urls && Array.isArray(meta.youtube_urls)) {
|
|
65
65
|
meta.youtube_urls.forEach((u) => typeof u === "string" && u.trim() && urls.push(u.trim()));
|
|
66
66
|
}
|
|
67
|
-
else if (meta?.youtube_url && typeof meta.youtube_url === "string") {
|
|
67
|
+
else if (meta?.youtube_url && typeof meta.youtube_url === "string" && meta.youtube_url.trim()) {
|
|
68
68
|
urls.push(meta.youtube_url.trim());
|
|
69
69
|
}
|
|
70
|
+
else if (meta?.youtube_link && typeof meta.youtube_link === "string" && meta.youtube_link.trim()) {
|
|
71
|
+
urls.push(meta.youtube_link.trim());
|
|
72
|
+
}
|
|
73
|
+
else if (meta?.video_url && typeof meta.video_url === "string" && meta.video_url.trim()) {
|
|
74
|
+
urls.push(meta.video_url.trim());
|
|
75
|
+
}
|
|
70
76
|
setYoutubeUrls(urls);
|
|
71
77
|
setActiveTab("general");
|
|
72
78
|
// Load category media from backend
|
|
@@ -93,8 +99,13 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
93
99
|
catch {
|
|
94
100
|
// Endpoint fallback
|
|
95
101
|
}
|
|
96
|
-
//
|
|
97
|
-
if (metadata
|
|
102
|
+
// Explicit metadata.images check: if metadata.images array exists (even if empty)
|
|
103
|
+
if (metadata && Array.isArray(metadata.images)) {
|
|
104
|
+
if (metadata.images.length === 0) {
|
|
105
|
+
setCategoryImages([]);
|
|
106
|
+
setImageUrl("");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
98
109
|
setCategoryImages(metadata.images);
|
|
99
110
|
const thumb = metadata.images.find((img) => img.type === "thumbnail") || metadata.images[0];
|
|
100
111
|
if (thumb?.url)
|
|
@@ -103,7 +114,7 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
103
114
|
}
|
|
104
115
|
// Legacy fallback
|
|
105
116
|
const list = [];
|
|
106
|
-
const thumb =
|
|
117
|
+
const thumb = metadata?.thumbnail || metadata?.image_url || currentImageUrl;
|
|
107
118
|
if (thumb) {
|
|
108
119
|
list.push({ url: thumb, type: "thumbnail" });
|
|
109
120
|
}
|
|
@@ -228,8 +239,26 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
228
239
|
setManualImageUrl("");
|
|
229
240
|
ui_1.toast.success(`Added to gallery as ${newType === "thumbnail" ? "Thumbnail" : newType === "banner" ? "Hero Banner" : "Image"}`);
|
|
230
241
|
};
|
|
231
|
-
// Delete category image
|
|
242
|
+
// Delete category image with immediate persistence & explicit nulls
|
|
232
243
|
const handleDeleteImage = async (img) => {
|
|
244
|
+
const remaining = categoryImages.filter((i) => i.url !== img.url && (img.id ? i.id !== img.id : true));
|
|
245
|
+
const nextThumb = remaining.find((i) => i.type === "thumbnail") || remaining[0] || null;
|
|
246
|
+
const nextBanner = remaining.find((i) => i.type === "banner") ||
|
|
247
|
+
(remaining.length > 1 ? remaining.find((i) => i.url !== nextThumb?.url) : null) ||
|
|
248
|
+
null;
|
|
249
|
+
// Update local state immediately
|
|
250
|
+
setCategoryImages(remaining);
|
|
251
|
+
setImageUrl(nextThumb ? nextThumb.url : "");
|
|
252
|
+
const updatedMeta = {
|
|
253
|
+
...(currentMetadata || {}),
|
|
254
|
+
images: remaining,
|
|
255
|
+
thumbnail: nextThumb ? nextThumb.url : null,
|
|
256
|
+
image_url: nextThumb ? nextThumb.url : null,
|
|
257
|
+
banner: nextBanner ? nextBanner.url : null,
|
|
258
|
+
image: (nextBanner?.url || nextThumb?.url) || null,
|
|
259
|
+
};
|
|
260
|
+
setCurrentMetadata(updatedMeta);
|
|
261
|
+
// Call backend endpoint if DB relation exists
|
|
233
262
|
if (img.id) {
|
|
234
263
|
try {
|
|
235
264
|
await sdk_1.sdk.client.fetch(`/admin/categories/${category.id}/images/batch`, {
|
|
@@ -240,16 +269,27 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
240
269
|
},
|
|
241
270
|
});
|
|
242
271
|
}
|
|
243
|
-
catch {
|
|
244
|
-
|
|
272
|
+
catch (err) {
|
|
273
|
+
console.warn("Could not delete from backend images endpoint:", err);
|
|
245
274
|
}
|
|
246
275
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
276
|
+
// Immediately persist metadata changes to Medusa
|
|
277
|
+
try {
|
|
278
|
+
await sdk_1.sdk.admin.productCategory.update(category.id, {
|
|
279
|
+
metadata: updatedMeta,
|
|
280
|
+
});
|
|
281
|
+
const updatedCat = {
|
|
282
|
+
...category,
|
|
283
|
+
image_url: nextThumb ? nextThumb.url : "",
|
|
284
|
+
metadata: updatedMeta,
|
|
285
|
+
};
|
|
286
|
+
onSuccess(updatedCat);
|
|
287
|
+
ui_1.toast.success("Image removed and changes saved");
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
console.warn("Could not immediately update category metadata on delete:", err);
|
|
291
|
+
ui_1.toast.info("Image removed from gallery (remember to save)");
|
|
251
292
|
}
|
|
252
|
-
ui_1.toast.info("Image removed");
|
|
253
293
|
};
|
|
254
294
|
if (!category)
|
|
255
295
|
return null;
|
|
@@ -258,7 +298,7 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
258
298
|
? prev.filter((id) => id !== channelId)
|
|
259
299
|
: [...prev, channelId]);
|
|
260
300
|
};
|
|
261
|
-
const handleAddYoutube = () => {
|
|
301
|
+
const handleAddYoutube = async () => {
|
|
262
302
|
const raw = newYoutubeInput.trim();
|
|
263
303
|
if (!raw)
|
|
264
304
|
return;
|
|
@@ -272,15 +312,59 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
272
312
|
ui_1.toast.error("Video already added");
|
|
273
313
|
return;
|
|
274
314
|
}
|
|
275
|
-
|
|
315
|
+
const nextUrls = [...youtubeUrls, cleanUrl];
|
|
316
|
+
setYoutubeUrls(nextUrls);
|
|
276
317
|
setNewYoutubeInput("");
|
|
277
|
-
|
|
318
|
+
const updatedMeta = {
|
|
319
|
+
...(currentMetadata || {}),
|
|
320
|
+
youtube_urls: nextUrls,
|
|
321
|
+
youtube_url: nextUrls[0] || null,
|
|
322
|
+
youtube_link: nextUrls[0] || null,
|
|
323
|
+
video_url: nextUrls[0] || null,
|
|
324
|
+
};
|
|
325
|
+
setCurrentMetadata(updatedMeta);
|
|
326
|
+
try {
|
|
327
|
+
await sdk_1.sdk.admin.productCategory.update(category.id, {
|
|
328
|
+
metadata: updatedMeta,
|
|
329
|
+
});
|
|
330
|
+
onSuccess({
|
|
331
|
+
...category,
|
|
332
|
+
metadata: updatedMeta,
|
|
333
|
+
});
|
|
334
|
+
ui_1.toast.success("YouTube video added and saved");
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
ui_1.toast.success("YouTube video added");
|
|
338
|
+
}
|
|
278
339
|
};
|
|
279
|
-
const handleRemoveYoutube = (urlToRemove) => {
|
|
280
|
-
|
|
340
|
+
const handleRemoveYoutube = async (urlToRemove) => {
|
|
341
|
+
const nextUrls = youtubeUrls.filter((u) => u !== urlToRemove);
|
|
342
|
+
setYoutubeUrls(nextUrls);
|
|
343
|
+
const updatedMeta = {
|
|
344
|
+
...(currentMetadata || {}),
|
|
345
|
+
youtube_urls: nextUrls,
|
|
346
|
+
youtube_url: nextUrls[0] || null,
|
|
347
|
+
youtube_link: nextUrls[0] || null,
|
|
348
|
+
video_url: nextUrls[0] || null,
|
|
349
|
+
};
|
|
350
|
+
setCurrentMetadata(updatedMeta);
|
|
351
|
+
try {
|
|
352
|
+
await sdk_1.sdk.admin.productCategory.update(category.id, {
|
|
353
|
+
metadata: updatedMeta,
|
|
354
|
+
});
|
|
355
|
+
onSuccess({
|
|
356
|
+
...category,
|
|
357
|
+
metadata: updatedMeta,
|
|
358
|
+
});
|
|
359
|
+
ui_1.toast.success("Video removed and changes saved");
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
ui_1.toast.info("Video removed (remember to save)");
|
|
363
|
+
}
|
|
281
364
|
};
|
|
282
365
|
const handleSubmit = async (e) => {
|
|
283
|
-
e.preventDefault
|
|
366
|
+
if (e && e.preventDefault)
|
|
367
|
+
e.preventDefault();
|
|
284
368
|
if (!name.trim()) {
|
|
285
369
|
ui_1.toast.error("Category name is required");
|
|
286
370
|
return;
|
|
@@ -288,26 +372,24 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
288
372
|
setIsSubmitting(true);
|
|
289
373
|
try {
|
|
290
374
|
const thumbImg = categoryImages.find((img) => img.type === "thumbnail") ||
|
|
291
|
-
categoryImages[0]
|
|
375
|
+
categoryImages[0] ||
|
|
376
|
+
null;
|
|
292
377
|
const bannerImg = categoryImages.find((img) => img.type === "banner") ||
|
|
293
|
-
categoryImages.find((img) => img.url !== thumbImg?.url)
|
|
378
|
+
(categoryImages.length > 1 ? categoryImages.find((img) => img.url !== thumbImg?.url) : null) ||
|
|
379
|
+
null;
|
|
294
380
|
const updatedMetadata = {
|
|
295
381
|
...(currentMetadata || {}),
|
|
296
382
|
sales_channels: selectedChannels,
|
|
297
383
|
youtube_urls: youtubeUrls,
|
|
384
|
+
youtube_url: youtubeUrls[0] || null,
|
|
385
|
+
youtube_link: youtubeUrls[0] || null,
|
|
386
|
+
video_url: youtubeUrls[0] || null,
|
|
298
387
|
images: categoryImages,
|
|
388
|
+
thumbnail: thumbImg ? thumbImg.url : null,
|
|
389
|
+
image_url: thumbImg ? thumbImg.url : null,
|
|
390
|
+
banner: bannerImg ? bannerImg.url : null,
|
|
391
|
+
image: (bannerImg?.url || thumbImg?.url) || null,
|
|
299
392
|
};
|
|
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
393
|
await sdk_1.sdk.admin.productCategory.update(category.id, {
|
|
312
394
|
name: name.trim(),
|
|
313
395
|
handle: handle.trim() || undefined,
|
|
@@ -322,7 +404,7 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
322
404
|
name: name.trim(),
|
|
323
405
|
handle: handle.trim(),
|
|
324
406
|
description: description.trim() || null,
|
|
325
|
-
image_url:
|
|
407
|
+
image_url: thumbImg ? thumbImg.url : "",
|
|
326
408
|
rank: Number(rank) || 0,
|
|
327
409
|
sales_channels: selectedChannels,
|
|
328
410
|
is_active: isActive,
|
|
@@ -360,15 +442,15 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
360
442
|
};
|
|
361
443
|
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
444
|
? "!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)("
|
|
364
|
-
? "border-
|
|
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
|
|
366
|
-
? "border-
|
|
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
|
|
368
|
-
? "border-
|
|
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
|
|
370
|
-
? "border-
|
|
371
|
-
: "border-transparent text-ui-fg-muted hover:text-ui-fg-base"}`, children: ["YouTube Videos
|
|
445
|
+
: "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"
|
|
446
|
+
? "border-blue-600 text-blue-600 font-semibold"
|
|
447
|
+
: "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"
|
|
448
|
+
? "border-blue-600 text-blue-600 font-semibold"
|
|
449
|
+
: "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"
|
|
450
|
+
? "border-blue-600 text-blue-600 font-semibold"
|
|
451
|
+
: "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"
|
|
452
|
+
? "border-blue-600 text-blue-600 font-semibold"
|
|
453
|
+
: "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
454
|
setCurrentMetadata(meta);
|
|
373
455
|
} }) })), 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
456
|
const isChecked = selectedChannels.includes(channel.id);
|
|
@@ -395,16 +477,16 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
395
477
|
e.preventDefault();
|
|
396
478
|
e.stopPropagation();
|
|
397
479
|
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) => {
|
|
480
|
+
}, 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
481
|
e.preventDefault();
|
|
400
482
|
e.stopPropagation();
|
|
401
483
|
handleSetBanner(img);
|
|
402
|
-
}, className: "w-full py-0.5 bg-emerald-
|
|
484
|
+
}, 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
485
|
e.preventDefault();
|
|
404
486
|
e.stopPropagation();
|
|
405
487
|
handleDeleteImage(img);
|
|
406
|
-
}, className: "
|
|
407
|
-
}) })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-1.5 mt-
|
|
488
|
+
}, 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));
|
|
489
|
+
}) })] })), (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
490
|
if (e.key === "Enter") {
|
|
409
491
|
e.preventDefault();
|
|
410
492
|
handleAddManualImage();
|
|
@@ -417,7 +499,7 @@ const EditCategoryDrawer = ({ open, onOpenChange, category, salesChannels, onSuc
|
|
|
417
499
|
} }), (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
500
|
const id = extractYouTubeVideoId(url);
|
|
419
501
|
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: "
|
|
502
|
+
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));
|
|
503
|
+
}) }))] }))] }), (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
504
|
};
|
|
423
505
|
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.
|
|
3
|
+
"version": "1.0.12",
|
|
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",
|