@zaamx/netme-bundle 0.0.4 → 0.0.5

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.
@@ -16,231 +16,309 @@ const sdk = new Medusa__default.default({
16
16
  type: "session"
17
17
  }
18
18
  });
19
+ const ProductSearchSelect = ({
20
+ value,
21
+ valueLabel,
22
+ onChange,
23
+ disabled,
24
+ placeholder = "Search products…"
25
+ }) => {
26
+ const [search, setSearch] = react.useState("");
27
+ const [open, setOpen] = react.useState(false);
28
+ const [results, setResults] = react.useState([]);
29
+ const [loading, setLoading] = react.useState(false);
30
+ const containerRef = react.useRef(null);
31
+ react.useEffect(() => {
32
+ const handler = (e) => {
33
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
34
+ setOpen(false);
35
+ }
36
+ };
37
+ document.addEventListener("mousedown", handler);
38
+ return () => document.removeEventListener("mousedown", handler);
39
+ }, []);
40
+ react.useEffect(() => {
41
+ if (!open) return;
42
+ const delay = search.trim() ? 300 : 0;
43
+ const timer = setTimeout(async () => {
44
+ setLoading(true);
45
+ try {
46
+ const { products } = await sdk.admin.product.list({
47
+ q: search.trim() || void 0,
48
+ limit: 15
49
+ });
50
+ setResults(products.map((p) => ({ id: p.id, title: p.title ?? "" })));
51
+ } catch {
52
+ setResults([]);
53
+ } finally {
54
+ setLoading(false);
55
+ }
56
+ }, delay);
57
+ return () => clearTimeout(timer);
58
+ }, [search, open]);
59
+ if (disabled) {
60
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border border-gray-200 rounded-md px-3 py-2 bg-gray-50 text-sm text-gray-700 min-h-[38px]", children: valueLabel || value || /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400", children: "—" }) });
61
+ }
62
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: "relative", children: [
63
+ value && valueLabel && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-1 flex items-center gap-2", children: [
64
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { className: "bg-blue-50 text-blue-700 text-xs", children: valueLabel }),
65
+ /* @__PURE__ */ jsxRuntime.jsx(
66
+ "button",
67
+ {
68
+ type: "button",
69
+ className: "text-xs text-gray-400 hover:text-gray-600",
70
+ onClick: () => onChange("", ""),
71
+ children: "✕ clear"
72
+ }
73
+ )
74
+ ] }),
75
+ /* @__PURE__ */ jsxRuntime.jsx(
76
+ ui.Input,
77
+ {
78
+ placeholder: value ? "Search to change…" : placeholder,
79
+ value: search,
80
+ onChange: (e) => {
81
+ setSearch(e.target.value);
82
+ setOpen(true);
83
+ },
84
+ onFocus: () => setOpen(true)
85
+ }
86
+ ),
87
+ open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute z-50 w-full mt-1 bg-white border border-gray-200 rounded-md shadow-lg max-h-60 overflow-y-auto", children: [
88
+ loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-gray-400", children: "Searching…" }),
89
+ !loading && results.length === 0 && search.trim() && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 py-2 text-sm text-gray-400", children: "No products found." }),
90
+ results.map((product) => /* @__PURE__ */ jsxRuntime.jsx(
91
+ "button",
92
+ {
93
+ type: "button",
94
+ className: "w-full text-left px-3 py-2 hover:bg-gray-50 text-sm border-b border-gray-100 last:border-0",
95
+ onMouseDown: (e) => {
96
+ e.preventDefault();
97
+ onChange(product.id, product.title);
98
+ setSearch("");
99
+ setOpen(false);
100
+ },
101
+ children: product.title
102
+ },
103
+ product.id
104
+ ))
105
+ ] })
106
+ ] });
107
+ };
108
+ const emptyBundleItem = () => ({
109
+ product_id: void 0,
110
+ product_title: "",
111
+ min_quantity: 1,
112
+ max_quantity: 1,
113
+ default_quantity: 1,
114
+ optional: false,
115
+ separate_shipping: false,
116
+ individual_price: false
117
+ });
118
+ const resetModal = {
119
+ isEditing: false,
120
+ selectedProductId: void 0,
121
+ selectedProductTitle: "",
122
+ bundleItems: [],
123
+ bundleMeta: []
124
+ };
19
125
  const limit = 15;
20
126
  const BundledProductsPage = () => {
21
127
  const [page, setPage] = react.useState(0);
22
128
  const [openCreateModal, setOpenCreateModal] = react.useState(false);
23
129
  const [isEditing, setIsEditing] = react.useState(false);
24
130
  const [selectedProductId, setSelectedProductId] = react.useState();
131
+ const [selectedProductTitle, setSelectedProductTitle] = react.useState("");
25
132
  const [bundleItems, setBundleItems] = react.useState([]);
26
133
  const [bundleMeta, setBundleMeta] = react.useState([]);
27
- const [products, setProducts] = react.useState([]);
28
- const productsLimit = 15;
29
- const [currentProductPage, setCurrentProductPage] = react.useState(0);
30
- const [productsCount, setProductsCount] = react.useState(0);
31
- const hasNextPage = react.useMemo(
32
- () => productsCount ? productsCount > productsLimit : true,
33
- [productsCount, productsLimit]
34
- );
35
134
  const queryClient = reactQuery.useQueryClient();
36
135
  const offset = page * limit;
136
+ const closeModal = () => {
137
+ setOpenCreateModal(false);
138
+ setIsEditing(resetModal.isEditing);
139
+ setSelectedProductId(resetModal.selectedProductId);
140
+ setSelectedProductTitle(resetModal.selectedProductTitle);
141
+ setBundleItems(resetModal.bundleItems);
142
+ setBundleMeta(resetModal.bundleMeta);
143
+ };
37
144
  const { data, isLoading } = reactQuery.useQuery({
38
145
  queryKey: ["bundled-products", offset, limit],
39
146
  queryFn: () => sdk.client.fetch("/admin/bundled-products", {
40
147
  method: "GET",
41
- query: {
42
- limit,
43
- offset
44
- }
148
+ query: { limit, offset }
45
149
  })
46
150
  });
47
- reactQuery.useQuery({
48
- queryKey: ["products"],
49
- queryFn: async () => {
50
- const { products: products2, count } = await sdk.admin.product.list({
51
- limit: productsLimit,
52
- offset: currentProductPage * productsLimit
53
- });
54
- setProductsCount(count);
55
- setProducts((prev) => [...prev, ...products2]);
56
- return products2;
57
- },
58
- enabled: hasNextPage
59
- });
60
- const fetchMoreProducts = () => {
61
- if (!hasNextPage) {
62
- return;
63
- }
64
- setCurrentProductPage(currentProductPage + 1);
65
- };
66
- const { mutateAsync: createBundle, isPending: isCreating } = reactQuery.useMutation({
67
- mutationFn: async (data2) => {
151
+ const { mutateAsync: saveBundle, isPending: isSaving } = reactQuery.useMutation({
152
+ mutationFn: async (payload) => {
68
153
  if (!selectedProductId) throw new Error("No product selected");
69
154
  await sdk.client.fetch(`/admin/products/${selectedProductId}/bundle`, {
70
155
  method: "POST",
71
- body: data2
156
+ body: {
157
+ child_product_ids: payload.child_product_ids.map((item) => ({
158
+ id: item.product_id,
159
+ min_quantity: item.min_quantity,
160
+ max_quantity: item.max_quantity,
161
+ default_quantity: item.default_quantity,
162
+ optional: item.optional,
163
+ separate_shipping: item.separate_shipping,
164
+ individual_price: item.individual_price
165
+ })),
166
+ bundle_meta: payload.bundle_meta,
167
+ is_bundle: payload.is_bundle
168
+ }
72
169
  });
73
170
  }
74
171
  });
75
- const { mutateAsync: deleteBundle, isPending: isDeleting } = reactQuery.useMutation({
172
+ const { mutateAsync: deleteBundle } = reactQuery.useMutation({
76
173
  mutationFn: async (productId) => {
77
- await sdk.client.fetch(`/admin/products/${productId}/bundle`, {
78
- method: "DELETE"
79
- });
174
+ await sdk.client.fetch(`/admin/products/${productId}/bundle`, { method: "DELETE" });
80
175
  }
81
176
  });
82
177
  const handleDeleteBundle = async (productId) => {
83
178
  try {
84
179
  await deleteBundle(productId);
85
180
  ui.toast.success("Bundle deleted successfully");
86
- queryClient.invalidateQueries({
87
- queryKey: ["bundled-products"]
88
- });
89
- } catch (error) {
90
- console.error("Error deleting bundle:", error);
181
+ queryClient.invalidateQueries({ queryKey: ["bundled-products"] });
182
+ } catch {
91
183
  ui.toast.error("Failed to delete bundle");
92
184
  }
93
185
  };
94
186
  const handleEditBundle = (bundle) => {
95
187
  setIsEditing(true);
96
188
  setSelectedProductId(bundle.product.id);
97
- setBundleItems(bundle.items.map((item) => ({
98
- product_id: item.product.id,
99
- min_quantity: item.min_quantity || 1,
100
- max_quantity: item.max_quantity || 1,
101
- default_quantity: item.quantity,
102
- optional: item.optional || false,
103
- separate_shipping: item.separate_shipping || false,
104
- individual_price: item.individual_price || false
105
- })));
189
+ setSelectedProductTitle(bundle.title);
190
+ setBundleItems(
191
+ bundle.items.map((item) => ({
192
+ product_id: item.product.id,
193
+ product_title: item.product.title,
194
+ min_quantity: item.min_quantity,
195
+ max_quantity: item.max_quantity,
196
+ default_quantity: item.default_quantity,
197
+ optional: item.optional,
198
+ separate_shipping: item.separate_shipping,
199
+ individual_price: item.individual_price
200
+ }))
201
+ );
106
202
  setBundleMeta(bundle.bundle_meta || []);
107
203
  setOpenCreateModal(true);
108
204
  };
109
- const handleCreateBundle = async () => {
205
+ const handleSaveBundle = async () => {
206
+ if (!selectedProductId) {
207
+ ui.toast.error("Please select a product");
208
+ return;
209
+ }
210
+ const validItems = bundleItems.filter((item) => item.product_id);
211
+ if (validItems.length === 0) {
212
+ ui.toast.error("Please add at least one product to the bundle");
213
+ return;
214
+ }
110
215
  try {
111
- if (!selectedProductId) {
112
- ui.toast.error("Please select a product");
113
- return;
114
- }
115
- const validItems = bundleItems.filter((item) => item.product_id);
116
- if (validItems.length === 0) {
117
- ui.toast.error("Please add at least one product to the bundle");
118
- return;
119
- }
120
- await createBundle({
121
- child_product_ids: validItems.map((item) => ({
122
- id: item.product_id,
123
- min_quantity: item.min_quantity,
124
- max_quantity: item.max_quantity,
125
- default_quantity: item.default_quantity,
126
- optional: item.optional,
127
- separate_shipping: item.separate_shipping,
128
- individual_price: item.individual_price
129
- })),
130
- bundle_meta: bundleMeta,
131
- is_bundle: true
132
- });
133
- setOpenCreateModal(false);
216
+ await saveBundle({ child_product_ids: validItems, bundle_meta: bundleMeta, is_bundle: true });
134
217
  ui.toast.success(isEditing ? "Bundle updated successfully" : "Bundle created successfully");
135
- queryClient.invalidateQueries({
136
- queryKey: ["bundled-products"]
137
- });
138
- setIsEditing(false);
139
- setSelectedProductId(void 0);
140
- setBundleItems([]);
141
- setBundleMeta([]);
142
- } catch (error) {
143
- console.error("Error creating bundle:", error);
144
- ui.toast.error("Failed to create bundle");
218
+ queryClient.invalidateQueries({ queryKey: ["bundled-products"] });
219
+ closeModal();
220
+ } catch {
221
+ ui.toast.error(isEditing ? "Failed to update bundle" : "Failed to create bundle");
145
222
  }
146
223
  };
147
- const addBundleItem = () => {
148
- setBundleItems([
149
- ...bundleItems,
150
- {
151
- product_id: void 0,
152
- min_quantity: 1,
153
- max_quantity: 1,
154
- default_quantity: 1,
155
- optional: false,
156
- separate_shipping: false,
157
- individual_price: false
158
- }
159
- ]);
160
- };
161
- const removeBundleItem = (index) => {
162
- setBundleItems(bundleItems.filter((_, i) => i !== index));
163
- };
164
224
  const updateBundleItem = (index, field, value) => {
165
- setBundleItems(bundleItems.map(
166
- (item, i) => i === index ? { ...item, [field]: value } : item
167
- ));
168
- };
169
- const addBundleMeta = () => {
170
- setBundleMeta([...bundleMeta, { key: "", value: "" }]);
171
- };
172
- const removeBundleMeta = (index) => {
173
- setBundleMeta(bundleMeta.filter((_, i) => i !== index));
174
- };
175
- const updateBundleMeta = (index, field, value) => {
176
- setBundleMeta(bundleMeta.map(
177
- (item, i) => i === index ? { ...item, [field]: value } : item
178
- ));
225
+ setBundleItems(
226
+ (prev) => prev.map((item, i) => i === index ? { ...item, [field]: value } : item)
227
+ );
179
228
  };
180
229
  const totalPages = Math.ceil(((data == null ? void 0 : data.count) || 0) / limit);
181
230
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
182
231
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2 md:flex-row md:items-center p-6", children: [
183
232
  /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: "Bundled Products" }),
184
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "primary", onClick: () => setOpenCreateModal(true), children: "Create Bundle" })
233
+ /* @__PURE__ */ jsxRuntime.jsx(
234
+ ui.Button,
235
+ {
236
+ variant: "primary",
237
+ onClick: () => {
238
+ closeModal();
239
+ setOpenCreateModal(true);
240
+ },
241
+ children: "Create Bundle"
242
+ }
243
+ )
185
244
  ] }),
186
- isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Loading..." }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6", children: [
245
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Loading" }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6", children: [
187
246
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full border-collapse border border-gray-200", children: [
188
247
  /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "bg-gray-50", children: [
189
- /* @__PURE__ */ jsxRuntime.jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "ID" }),
190
- /* @__PURE__ */ jsxRuntime.jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Title" }),
191
- /* @__PURE__ */ jsxRuntime.jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Items" }),
192
248
  /* @__PURE__ */ jsxRuntime.jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Product" }),
193
- /* @__PURE__ */ jsxRuntime.jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Status" })
249
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Status" }),
250
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Items" }),
251
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "border border-gray-200 px-4 py-2 text-left", children: "Actions" })
194
252
  ] }) }),
195
- /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: data == null ? void 0 : data.bundled_products.map((bundle) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "hover:bg-gray-50", children: [
196
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "border border-gray-200 px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "text-sm", children: bundle.id }) }),
197
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "border border-gray-200 px-4 py-2", children: bundle.title }),
198
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "border border-gray-200 px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1", children: bundle.items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm", children: [
199
- /* @__PURE__ */ jsxRuntime.jsx(
200
- reactRouterDom.Link,
201
- {
202
- to: `/products/${item.product.id}`,
203
- className: "text-blue-600 hover:underline",
204
- children: item.product.title
205
- }
206
- ),
207
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-500", children: [
208
- " x ",
209
- item.quantity
210
- ] }),
211
- item.optional && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { className: "ml-2 bg-gray-100 text-gray-700", children: "Optional" })
212
- ] }, item.id)) }) }),
213
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "border border-gray-200 px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsx(
214
- reactRouterDom.Link,
253
+ /* @__PURE__ */ jsxRuntime.jsxs("tbody", { children: [
254
+ ((data == null ? void 0 : data.bundled_products.length) ?? 0) === 0 && /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
255
+ "td",
215
256
  {
216
- to: `/products/${bundle.product.id}`,
217
- className: "text-blue-600 hover:underline",
218
- children: "View Product"
257
+ colSpan: 4,
258
+ className: "border border-gray-200 px-4 py-6 text-center text-gray-500",
259
+ children: "No bundled products found."
219
260
  }
220
261
  ) }),
221
- /* @__PURE__ */ jsxRuntime.jsx("td", { className: "border border-gray-200 px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
222
- /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { className: bundle.is_bundle ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-700", children: bundle.is_bundle ? "Active" : "Inactive" }),
223
- /* @__PURE__ */ jsxRuntime.jsx(
224
- ui.Button,
225
- {
226
- variant: "secondary",
227
- size: "small",
228
- onClick: () => handleEditBundle(bundle),
229
- children: "Edit"
230
- }
231
- ),
232
- /* @__PURE__ */ jsxRuntime.jsx(
233
- ui.Button,
262
+ data == null ? void 0 : data.bundled_products.map((bundle) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "hover:bg-gray-50", children: [
263
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "border border-gray-200 px-4 py-3", children: [
264
+ /* @__PURE__ */ jsxRuntime.jsx(
265
+ reactRouterDom.Link,
266
+ {
267
+ to: `/products/${bundle.product.id}`,
268
+ className: "font-medium text-blue-600 hover:underline",
269
+ children: bundle.title
270
+ }
271
+ ),
272
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-400 mt-0.5", children: bundle.id })
273
+ ] }),
274
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "border border-gray-200 px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(
275
+ ui.Badge,
234
276
  {
235
- variant: "secondary",
236
- size: "small",
237
- onClick: () => handleDeleteBundle(bundle.id),
238
- className: "text-red-600 hover:text-red-700",
239
- children: "Delete"
277
+ className: bundle.status === "published" ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-600",
278
+ children: bundle.status ?? ""
240
279
  }
241
- )
242
- ] }) })
243
- ] }, bundle.id)) })
280
+ ) }),
281
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "border border-gray-200 px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
282
+ bundle.items.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-400", children: "No items" }),
283
+ bundle.items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm flex items-center gap-1", children: [
284
+ /* @__PURE__ */ jsxRuntime.jsx(
285
+ reactRouterDom.Link,
286
+ {
287
+ to: `/products/${item.product.id}`,
288
+ className: "text-blue-600 hover:underline",
289
+ children: item.product.title
290
+ }
291
+ ),
292
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-400", children: [
293
+ "× ",
294
+ item.default_quantity
295
+ ] }),
296
+ item.optional && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { className: "bg-gray-100 text-gray-500 text-xs", children: "Optional" })
297
+ ] }, item.id))
298
+ ] }) }),
299
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "border border-gray-200 px-4 py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
300
+ /* @__PURE__ */ jsxRuntime.jsx(
301
+ ui.Button,
302
+ {
303
+ variant: "secondary",
304
+ size: "small",
305
+ onClick: () => handleEditBundle(bundle),
306
+ children: "Edit"
307
+ }
308
+ ),
309
+ /* @__PURE__ */ jsxRuntime.jsx(
310
+ ui.Button,
311
+ {
312
+ variant: "secondary",
313
+ size: "small",
314
+ onClick: () => handleDeleteBundle(bundle.id),
315
+ className: "text-red-600 hover:text-red-700",
316
+ children: "Delete"
317
+ }
318
+ )
319
+ ] }) })
320
+ ] }, bundle.id))
321
+ ] })
244
322
  ] }) }),
245
323
  totalPages > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-center gap-2 mt-6", children: [
246
324
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -269,46 +347,39 @@ const BundledProductsPage = () => {
269
347
  )
270
348
  ] })
271
349
  ] }),
272
- /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal, { open: openCreateModal, onOpenChange: setOpenCreateModal, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Content, { children: [
350
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal, { open: openCreateModal, onOpenChange: (open) => {
351
+ if (!open) closeModal();
352
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Content, { children: [
273
353
  /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: isEditing ? "Edit Bundle" : "Create Bundle" }) }),
274
354
  /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Body, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 flex-col items-center h-[80vh] overflow-y-auto px-2 py-8", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-3xl flex flex-col gap-y-8", children: [
275
355
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
276
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Select Product to Bundle" }),
277
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-gray-600 mb-4", children: "Choose a product that will become a bundle" }),
278
- /* @__PURE__ */ jsxRuntime.jsxs(
279
- ui.Select,
356
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Bundle Product" }),
357
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-gray-500 mb-2", children: isEditing ? "The product this bundle is attached to" : "Choose the product that will become a bundle" }),
358
+ /* @__PURE__ */ jsxRuntime.jsx(
359
+ ProductSearchSelect,
280
360
  {
281
361
  value: selectedProductId,
282
- onValueChange: setSelectedProductId,
362
+ valueLabel: selectedProductTitle,
363
+ onChange: (id, title) => {
364
+ setSelectedProductId(id || void 0);
365
+ setSelectedProductTitle(title);
366
+ },
283
367
  disabled: isEditing,
284
- children: [
285
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select a product to bundle" }) }),
286
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: products == null ? void 0 : products.map((product) => /* @__PURE__ */ jsxRuntime.jsx(
287
- ui.Select.Item,
288
- {
289
- value: product.id,
290
- children: product.title
291
- },
292
- product.id
293
- )) })
294
- ]
368
+ placeholder: "Search for a product to bundle…"
295
369
  }
296
370
  )
297
371
  ] }),
298
372
  selectedProductId && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
299
373
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
300
374
  /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Bundle Items" }),
301
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-gray-600 mb-4", children: "Select products to include in this bundle" }),
375
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-gray-500 mb-4", children: "Products included in this bundle" }),
302
376
  bundleItems.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
303
377
  BundleItemForm,
304
378
  {
305
379
  item,
306
380
  index,
307
- products,
308
381
  onUpdate: (field, value) => updateBundleItem(index, field, value),
309
- onRemove: () => removeBundleItem(index),
310
- fetchMoreProducts,
311
- hasNextPage
382
+ onRemove: () => setBundleItems((prev) => prev.filter((_, i) => i !== index))
312
383
  },
313
384
  index
314
385
  )),
@@ -316,7 +387,7 @@ const BundledProductsPage = () => {
316
387
  ui.Button,
317
388
  {
318
389
  variant: "secondary",
319
- onClick: addBundleItem,
390
+ onClick: () => setBundleItems((prev) => [...prev, emptyBundleItem()]),
320
391
  className: "mt-4",
321
392
  children: "Add Item"
322
393
  }
@@ -324,14 +395,16 @@ const BundledProductsPage = () => {
324
395
  ] }),
325
396
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
326
397
  /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Bundle Metadata" }),
327
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-gray-600 mb-4", children: "Add custom metadata for this bundle" }),
398
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-gray-500 mb-4", children: "Custom key-value pairs for this bundle" }),
328
399
  bundleMeta.map((meta, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 mb-2", children: [
329
400
  /* @__PURE__ */ jsxRuntime.jsx(
330
401
  ui.Input,
331
402
  {
332
403
  placeholder: "Key",
333
404
  value: meta.key,
334
- onChange: (e) => updateBundleMeta(index, "key", e.target.value)
405
+ onChange: (e) => setBundleMeta(
406
+ (prev) => prev.map((m, i) => i === index ? { ...m, key: e.target.value } : m)
407
+ )
335
408
  }
336
409
  ),
337
410
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -339,14 +412,18 @@ const BundledProductsPage = () => {
339
412
  {
340
413
  placeholder: "Value",
341
414
  value: meta.value,
342
- onChange: (e) => updateBundleMeta(index, "value", e.target.value)
415
+ onChange: (e) => setBundleMeta(
416
+ (prev) => prev.map(
417
+ (m, i) => i === index ? { ...m, value: e.target.value } : m
418
+ )
419
+ )
343
420
  }
344
421
  ),
345
422
  /* @__PURE__ */ jsxRuntime.jsx(
346
423
  ui.Button,
347
424
  {
348
425
  variant: "secondary",
349
- onClick: () => removeBundleMeta(index),
426
+ onClick: () => setBundleMeta((prev) => prev.filter((_, i) => i !== index)),
350
427
  children: "Remove"
351
428
  }
352
429
  )
@@ -355,7 +432,7 @@ const BundledProductsPage = () => {
355
432
  ui.Button,
356
433
  {
357
434
  variant: "secondary",
358
- onClick: addBundleMeta,
435
+ onClick: () => setBundleMeta((prev) => [...prev, { key: "", value: "" }]),
359
436
  className: "mt-4",
360
437
  children: "Add Metadata"
361
438
  }
@@ -364,19 +441,13 @@ const BundledProductsPage = () => {
364
441
  ] })
365
442
  ] }) }) }),
366
443
  /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Footer, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
367
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => {
368
- setOpenCreateModal(false);
369
- setIsEditing(false);
370
- setSelectedProductId(void 0);
371
- setBundleItems([]);
372
- setBundleMeta([]);
373
- }, children: "Cancel" }),
444
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: closeModal, children: "Cancel" }),
374
445
  /* @__PURE__ */ jsxRuntime.jsx(
375
446
  ui.Button,
376
447
  {
377
448
  variant: "primary",
378
- onClick: handleCreateBundle,
379
- isLoading: isCreating,
449
+ onClick: handleSaveBundle,
450
+ isLoading: isSaving,
380
451
  disabled: !selectedProductId,
381
452
  children: isEditing ? "Update Bundle" : "Create Bundle"
382
453
  }
@@ -385,145 +456,100 @@ const BundledProductsPage = () => {
385
456
  ] }) })
386
457
  ] });
387
458
  };
388
- const BundleItemForm = ({
389
- item,
390
- index,
391
- products,
392
- onUpdate,
393
- onRemove,
394
- fetchMoreProducts,
395
- hasNextPage
396
- }) => {
397
- const observer = react.useRef(
398
- new IntersectionObserver(
399
- (entries) => {
400
- if (!hasNextPage) {
401
- return;
459
+ const BundleItemForm = ({ item, index, onUpdate, onRemove }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border rounded-lg p-4 space-y-4 mb-4", children: [
460
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
461
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Heading, { level: "h3", children: [
462
+ "Item ",
463
+ index + 1
464
+ ] }),
465
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: onRemove, children: "Remove" })
466
+ ] }),
467
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
468
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Product" }),
469
+ /* @__PURE__ */ jsxRuntime.jsx(
470
+ ProductSearchSelect,
471
+ {
472
+ value: item.product_id,
473
+ valueLabel: item.product_title,
474
+ onChange: (id, title) => {
475
+ onUpdate("product_id", id || void 0);
476
+ onUpdate("product_title", title);
477
+ },
478
+ placeholder: "Search for a product…"
479
+ }
480
+ )
481
+ ] }),
482
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-3 gap-4", children: [
483
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
484
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Min Quantity" }),
485
+ /* @__PURE__ */ jsxRuntime.jsx(
486
+ ui.Input,
487
+ {
488
+ type: "number",
489
+ min: 0,
490
+ value: item.min_quantity,
491
+ onChange: (e) => onUpdate("min_quantity", parseInt(e.target.value) || 0)
402
492
  }
403
- const first = entries[0];
404
- if (first.isIntersecting) {
405
- fetchMoreProducts();
493
+ )
494
+ ] }),
495
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
496
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Max Quantity" }),
497
+ /* @__PURE__ */ jsxRuntime.jsx(
498
+ ui.Input,
499
+ {
500
+ type: "number",
501
+ min: 0,
502
+ value: item.max_quantity,
503
+ onChange: (e) => onUpdate("max_quantity", parseInt(e.target.value) || 0)
406
504
  }
407
- },
408
- { threshold: 1 }
409
- )
410
- );
411
- const lastOptionRef = react.useCallback(
412
- (node) => {
413
- if (!hasNextPage) {
414
- return;
415
- }
416
- if (observer.current) {
417
- observer.current.disconnect();
418
- }
419
- if (node) {
420
- observer.current.observe(node);
421
- }
422
- },
423
- [hasNextPage]
424
- );
425
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border rounded-lg p-4 space-y-4", children: [
426
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
427
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Heading, { level: "h3", children: [
428
- "Item ",
429
- index + 1
430
- ] }),
431
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: onRemove, children: "Remove" })
505
+ )
432
506
  ] }),
433
507
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
434
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Product" }),
435
- /* @__PURE__ */ jsxRuntime.jsxs(
436
- ui.Select,
508
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Default Quantity" }),
509
+ /* @__PURE__ */ jsxRuntime.jsx(
510
+ ui.Input,
437
511
  {
438
- value: item.product_id,
439
- onValueChange: (value) => onUpdate("product_id", value),
440
- children: [
441
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select Product" }) }),
442
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: products == null ? void 0 : products.map((product, productIndex) => /* @__PURE__ */ jsxRuntime.jsx(
443
- ui.Select.Item,
444
- {
445
- value: product.id,
446
- ref: productIndex === products.length - 1 ? lastOptionRef : null,
447
- children: product.title
448
- },
449
- product.id
450
- )) })
451
- ]
512
+ type: "number",
513
+ min: 0,
514
+ value: item.default_quantity,
515
+ onChange: (e) => onUpdate("default_quantity", parseInt(e.target.value) || 0)
452
516
  }
453
517
  )
518
+ ] })
519
+ ] }),
520
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
521
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
522
+ /* @__PURE__ */ jsxRuntime.jsx(
523
+ ui.Switch,
524
+ {
525
+ checked: item.optional,
526
+ onCheckedChange: (checked) => onUpdate("optional", checked)
527
+ }
528
+ ),
529
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Optional" })
454
530
  ] }),
455
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-3 gap-4", children: [
456
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
457
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Min Quantity" }),
458
- /* @__PURE__ */ jsxRuntime.jsx(
459
- ui.Input,
460
- {
461
- type: "number",
462
- min: 0,
463
- value: item.min_quantity,
464
- onChange: (e) => onUpdate("min_quantity", parseInt(e.target.value) || 0)
465
- }
466
- )
467
- ] }),
468
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
469
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Max Quantity" }),
470
- /* @__PURE__ */ jsxRuntime.jsx(
471
- ui.Input,
472
- {
473
- type: "number",
474
- min: 0,
475
- value: item.max_quantity,
476
- onChange: (e) => onUpdate("max_quantity", parseInt(e.target.value) || 0)
477
- }
478
- )
479
- ] }),
480
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
481
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Default Quantity" }),
482
- /* @__PURE__ */ jsxRuntime.jsx(
483
- ui.Input,
484
- {
485
- type: "number",
486
- min: 0,
487
- value: item.default_quantity,
488
- onChange: (e) => onUpdate("default_quantity", parseInt(e.target.value) || 0)
489
- }
490
- )
491
- ] })
531
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
532
+ /* @__PURE__ */ jsxRuntime.jsx(
533
+ ui.Switch,
534
+ {
535
+ checked: item.separate_shipping,
536
+ onCheckedChange: (checked) => onUpdate("separate_shipping", checked)
537
+ }
538
+ ),
539
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Separate Shipping" })
492
540
  ] }),
493
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
494
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
495
- /* @__PURE__ */ jsxRuntime.jsx(
496
- ui.Switch,
497
- {
498
- checked: item.optional,
499
- onCheckedChange: (checked) => onUpdate("optional", checked)
500
- }
501
- ),
502
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Optional" })
503
- ] }),
504
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
505
- /* @__PURE__ */ jsxRuntime.jsx(
506
- ui.Switch,
507
- {
508
- checked: item.separate_shipping,
509
- onCheckedChange: (checked) => onUpdate("separate_shipping", checked)
510
- }
511
- ),
512
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Separate Shipping" })
513
- ] }),
514
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
515
- /* @__PURE__ */ jsxRuntime.jsx(
516
- ui.Switch,
517
- {
518
- checked: item.individual_price,
519
- onCheckedChange: (checked) => onUpdate("individual_price", checked)
520
- }
521
- ),
522
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Individual Price" })
523
- ] })
541
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
542
+ /* @__PURE__ */ jsxRuntime.jsx(
543
+ ui.Switch,
544
+ {
545
+ checked: item.individual_price,
546
+ onCheckedChange: (checked) => onUpdate("individual_price", checked)
547
+ }
548
+ ),
549
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Individual Price" })
524
550
  ] })
525
- ] });
526
- };
551
+ ] })
552
+ ] });
527
553
  const config = adminSdk.defineRouteConfig({
528
554
  label: "Bundled Products",
529
555
  icon: icons.CubeSolid