@lodashventure/medusa-brand 1.2.10 → 1.2.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.
|
@@ -1,987 +0,0 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
|
|
3
|
-
import Medusa, { FetchError } from "@medusajs/js-sdk";
|
|
4
|
-
import { Container, Heading, Alert, Text, Button, Label, Select, Badge, Input, Textarea, Switch, clx, Table, DropdownMenu, IconButton } from "@medusajs/ui";
|
|
5
|
-
import { BuildingStorefront, Trash, Link, X, CloudArrowUp, PhotoSolid, Plus, MagnifyingGlass, EllipsisHorizontal, PencilSquare } from "@medusajs/icons";
|
|
6
|
-
import { useState, useEffect, useCallback } from "react";
|
|
7
|
-
const sdk = new Medusa({
|
|
8
|
-
baseUrl: "/",
|
|
9
|
-
debug: false,
|
|
10
|
-
auth: {
|
|
11
|
-
type: "session"
|
|
12
|
-
}
|
|
13
|
-
});
|
|
14
|
-
const ProductBrandWidget = ({ data: product }) => {
|
|
15
|
-
const [brands, setBrands] = useState([]);
|
|
16
|
-
const [currentBrand, setCurrentBrand] = useState(null);
|
|
17
|
-
const [selectedBrandId, setSelectedBrandId] = useState("none");
|
|
18
|
-
const [loading, setLoading] = useState(false);
|
|
19
|
-
const [error, setError] = useState(null);
|
|
20
|
-
const [success, setSuccess] = useState(null);
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
fetchBrands();
|
|
23
|
-
fetchProductBrand();
|
|
24
|
-
}, [product.id]);
|
|
25
|
-
const fetchBrands = async () => {
|
|
26
|
-
try {
|
|
27
|
-
const response = await sdk.client.fetch(
|
|
28
|
-
"/admin/brands?is_active=true&limit=100",
|
|
29
|
-
{}
|
|
30
|
-
);
|
|
31
|
-
if (response.ok) {
|
|
32
|
-
const data = await response.json();
|
|
33
|
-
setBrands(data.brands || []);
|
|
34
|
-
}
|
|
35
|
-
} catch (err) {
|
|
36
|
-
if (err instanceof FetchError && err.status === 404) {
|
|
37
|
-
setBrands([]);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
console.error("Error fetching brands:", err);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
const fetchProductBrand = async () => {
|
|
44
|
-
try {
|
|
45
|
-
const response = await sdk.client.fetch(
|
|
46
|
-
`/admin/products/${product.id}/brand`,
|
|
47
|
-
{}
|
|
48
|
-
);
|
|
49
|
-
if (response.ok) {
|
|
50
|
-
const data = await response.json();
|
|
51
|
-
if (data.brand) {
|
|
52
|
-
setCurrentBrand(data.brand);
|
|
53
|
-
setSelectedBrandId(data.brand.id);
|
|
54
|
-
} else {
|
|
55
|
-
setCurrentBrand(null);
|
|
56
|
-
setSelectedBrandId("none");
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
} catch (err) {
|
|
60
|
-
if (err instanceof FetchError && err.status === 404) {
|
|
61
|
-
setCurrentBrand(null);
|
|
62
|
-
setSelectedBrandId("none");
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
console.error("Error fetching product brand:", err);
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
const handleBrandChange = (brandId) => {
|
|
69
|
-
setSelectedBrandId(brandId);
|
|
70
|
-
setError(null);
|
|
71
|
-
setSuccess(null);
|
|
72
|
-
};
|
|
73
|
-
const handleSaveBrand = async () => {
|
|
74
|
-
if (!selectedBrandId || selectedBrandId === "none") {
|
|
75
|
-
if (currentBrand) {
|
|
76
|
-
await handleRemoveBrand();
|
|
77
|
-
}
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
setLoading(true);
|
|
81
|
-
setError(null);
|
|
82
|
-
setSuccess(null);
|
|
83
|
-
try {
|
|
84
|
-
const response = await sdk.client.fetch(
|
|
85
|
-
`/admin/products/${product.id}/brand`,
|
|
86
|
-
{
|
|
87
|
-
method: "POST",
|
|
88
|
-
headers: {
|
|
89
|
-
"Content-Type": "application/json"
|
|
90
|
-
},
|
|
91
|
-
body: JSON.stringify({ brand_id: selectedBrandId })
|
|
92
|
-
}
|
|
93
|
-
);
|
|
94
|
-
if (response.ok) {
|
|
95
|
-
setSuccess("Brand updated successfully");
|
|
96
|
-
fetchProductBrand();
|
|
97
|
-
} else {
|
|
98
|
-
const data = await response.json();
|
|
99
|
-
setError(data.error || "Failed to update brand");
|
|
100
|
-
}
|
|
101
|
-
} catch (err) {
|
|
102
|
-
setError("Error updating brand");
|
|
103
|
-
console.error("Error updating brand:", err);
|
|
104
|
-
} finally {
|
|
105
|
-
setLoading(false);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
const handleRemoveBrand = async () => {
|
|
109
|
-
if (!confirm("Are you sure you want to remove the brand from this product?")) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
setLoading(true);
|
|
113
|
-
setError(null);
|
|
114
|
-
setSuccess(null);
|
|
115
|
-
try {
|
|
116
|
-
const response = await sdk.client.fetch(
|
|
117
|
-
`/admin/products/${product.id}/brand`,
|
|
118
|
-
{
|
|
119
|
-
method: "DELETE"
|
|
120
|
-
}
|
|
121
|
-
);
|
|
122
|
-
if (response.ok) {
|
|
123
|
-
setSuccess("Brand removed successfully");
|
|
124
|
-
setCurrentBrand(null);
|
|
125
|
-
setSelectedBrandId("");
|
|
126
|
-
} else {
|
|
127
|
-
const data = await response.json();
|
|
128
|
-
setError(data.error || "Failed to remove brand");
|
|
129
|
-
}
|
|
130
|
-
} catch (err) {
|
|
131
|
-
setError("Error removing brand");
|
|
132
|
-
console.error("Error removing brand:", err);
|
|
133
|
-
} finally {
|
|
134
|
-
setLoading(false);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
const hasChanges = selectedBrandId !== ((currentBrand == null ? void 0 : currentBrand.id) || "none");
|
|
138
|
-
return /* @__PURE__ */ jsx(Container, { className: "divide-y px-0 pb-0 pt-0", children: /* @__PURE__ */ jsxs("div", { className: "px-6 py-6", children: [
|
|
139
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
|
|
140
|
-
/* @__PURE__ */ jsx(BuildingStorefront, { className: "text-ui-fg-subtle" }),
|
|
141
|
-
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Brand" })
|
|
142
|
-
] }),
|
|
143
|
-
error && /* @__PURE__ */ jsx(Alert, { variant: "error", dismissible: true, className: "mb-4", children: error }),
|
|
144
|
-
success && /* @__PURE__ */ jsx(Alert, { variant: "success", dismissible: true, className: "mb-4", children: success }),
|
|
145
|
-
currentBrand && !hasChanges && /* @__PURE__ */ jsx("div", { className: "mb-4 p-4 rounded-lg border bg-ui-bg-subtle", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
146
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
147
|
-
currentBrand.logo ? /* @__PURE__ */ jsx(
|
|
148
|
-
"img",
|
|
149
|
-
{
|
|
150
|
-
src: currentBrand.logo,
|
|
151
|
-
alt: currentBrand.name,
|
|
152
|
-
className: "h-10 w-10 object-contain rounded"
|
|
153
|
-
}
|
|
154
|
-
) : /* @__PURE__ */ jsx("div", { className: "h-10 w-10 rounded bg-ui-bg-base flex items-center justify-center", children: /* @__PURE__ */ jsx(BuildingStorefront, { className: "h-5 w-5 text-ui-fg-subtle" }) }),
|
|
155
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
156
|
-
/* @__PURE__ */ jsx(Text, { className: "font-medium", children: currentBrand.name }),
|
|
157
|
-
/* @__PURE__ */ jsxs(Text, { className: "text-sm text-ui-fg-subtle", children: [
|
|
158
|
-
"/",
|
|
159
|
-
currentBrand.slug
|
|
160
|
-
] })
|
|
161
|
-
] })
|
|
162
|
-
] }),
|
|
163
|
-
/* @__PURE__ */ jsxs(
|
|
164
|
-
Button,
|
|
165
|
-
{
|
|
166
|
-
variant: "danger",
|
|
167
|
-
size: "small",
|
|
168
|
-
onClick: handleRemoveBrand,
|
|
169
|
-
disabled: loading,
|
|
170
|
-
children: [
|
|
171
|
-
/* @__PURE__ */ jsx(Trash, { className: "mr-1" }),
|
|
172
|
-
"Remove"
|
|
173
|
-
]
|
|
174
|
-
}
|
|
175
|
-
)
|
|
176
|
-
] }) }),
|
|
177
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
178
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
179
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "brand", className: "mb-2", children: "Select Brand" }),
|
|
180
|
-
/* @__PURE__ */ jsxs(
|
|
181
|
-
Select,
|
|
182
|
-
{
|
|
183
|
-
value: selectedBrandId,
|
|
184
|
-
onValueChange: handleBrandChange,
|
|
185
|
-
disabled: loading,
|
|
186
|
-
children: [
|
|
187
|
-
/* @__PURE__ */ jsx(Select.Trigger, { id: "brand", children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Choose a brand..." }) }),
|
|
188
|
-
/* @__PURE__ */ jsxs(Select.Content, { children: [
|
|
189
|
-
/* @__PURE__ */ jsx(Select.Item, { value: "none", children: /* @__PURE__ */ jsx("span", { className: "text-ui-fg-muted", children: "No brand" }) }),
|
|
190
|
-
brands.map((brand) => /* @__PURE__ */ jsx(Select.Item, { value: brand.id, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
191
|
-
brand.logo && /* @__PURE__ */ jsx(
|
|
192
|
-
"img",
|
|
193
|
-
{
|
|
194
|
-
src: brand.logo,
|
|
195
|
-
alt: brand.name,
|
|
196
|
-
className: "h-5 w-5 object-contain rounded"
|
|
197
|
-
}
|
|
198
|
-
),
|
|
199
|
-
/* @__PURE__ */ jsx("span", { children: brand.name }),
|
|
200
|
-
/* @__PURE__ */ jsx(Badge, { size: "xsmall", color: "blue", className: "ml-auto", children: brand.slug })
|
|
201
|
-
] }) }, brand.id))
|
|
202
|
-
] })
|
|
203
|
-
]
|
|
204
|
-
}
|
|
205
|
-
),
|
|
206
|
-
/* @__PURE__ */ jsx(Text, { className: "mt-1 text-xs text-ui-fg-subtle", children: "Assign a brand to help customers find products by their favorite brands" })
|
|
207
|
-
] }),
|
|
208
|
-
hasChanges && /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
209
|
-
/* @__PURE__ */ jsx(
|
|
210
|
-
Button,
|
|
211
|
-
{
|
|
212
|
-
variant: "primary",
|
|
213
|
-
size: "small",
|
|
214
|
-
onClick: handleSaveBrand,
|
|
215
|
-
disabled: loading,
|
|
216
|
-
children: loading ? "Saving..." : "Save Brand"
|
|
217
|
-
}
|
|
218
|
-
),
|
|
219
|
-
/* @__PURE__ */ jsx(
|
|
220
|
-
Button,
|
|
221
|
-
{
|
|
222
|
-
variant: "secondary",
|
|
223
|
-
size: "small",
|
|
224
|
-
onClick: () => {
|
|
225
|
-
setSelectedBrandId((currentBrand == null ? void 0 : currentBrand.id) || "none");
|
|
226
|
-
setError(null);
|
|
227
|
-
setSuccess(null);
|
|
228
|
-
},
|
|
229
|
-
disabled: loading,
|
|
230
|
-
children: "Cancel"
|
|
231
|
-
}
|
|
232
|
-
)
|
|
233
|
-
] })
|
|
234
|
-
] }),
|
|
235
|
-
/* @__PURE__ */ jsx("div", { className: "mt-6 pt-6 border-t", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
|
|
236
|
-
/* @__PURE__ */ jsx(Link, { className: "text-ui-fg-subtle" }),
|
|
237
|
-
/* @__PURE__ */ jsx(
|
|
238
|
-
"a",
|
|
239
|
-
{
|
|
240
|
-
href: "/app/brands",
|
|
241
|
-
className: "text-ui-fg-interactive hover:text-ui-fg-interactive-hover transition-colors",
|
|
242
|
-
children: "Manage all brands"
|
|
243
|
-
}
|
|
244
|
-
)
|
|
245
|
-
] }) })
|
|
246
|
-
] }) });
|
|
247
|
-
};
|
|
248
|
-
defineWidgetConfig({
|
|
249
|
-
zone: "product.details.side.after"
|
|
250
|
-
});
|
|
251
|
-
const BrandForm = ({ brand, isCreating, onClose }) => {
|
|
252
|
-
const [formData, setFormData] = useState({
|
|
253
|
-
name: "",
|
|
254
|
-
slug: "",
|
|
255
|
-
description: "",
|
|
256
|
-
website: "",
|
|
257
|
-
is_active: true,
|
|
258
|
-
metadata: {}
|
|
259
|
-
});
|
|
260
|
-
const [loading, setLoading] = useState(false);
|
|
261
|
-
const [error, setError] = useState(null);
|
|
262
|
-
const [slugError, setSlugError] = useState(null);
|
|
263
|
-
useEffect(() => {
|
|
264
|
-
if (brand) {
|
|
265
|
-
setFormData({
|
|
266
|
-
name: brand.name || "",
|
|
267
|
-
slug: brand.slug || "",
|
|
268
|
-
description: brand.description || "",
|
|
269
|
-
website: brand.website || "",
|
|
270
|
-
is_active: brand.is_active !== false,
|
|
271
|
-
metadata: brand.metadata || {}
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}, [brand]);
|
|
275
|
-
const generateSlug = (name) => {
|
|
276
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
277
|
-
};
|
|
278
|
-
const handleNameChange = (value) => {
|
|
279
|
-
setFormData((prev) => ({
|
|
280
|
-
...prev,
|
|
281
|
-
name: value,
|
|
282
|
-
slug: isCreating ? generateSlug(value) : prev.slug
|
|
283
|
-
}));
|
|
284
|
-
};
|
|
285
|
-
const handleSlugChange = (value) => {
|
|
286
|
-
const cleanSlug = value.toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/--+/g, "-");
|
|
287
|
-
setFormData((prev) => ({ ...prev, slug: cleanSlug }));
|
|
288
|
-
if (slugError) setSlugError(null);
|
|
289
|
-
};
|
|
290
|
-
const validateForm = () => {
|
|
291
|
-
if (!formData.name.trim()) {
|
|
292
|
-
setError("Brand name is required");
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
295
|
-
if (!formData.slug.trim()) {
|
|
296
|
-
setError("Brand slug is required");
|
|
297
|
-
return false;
|
|
298
|
-
}
|
|
299
|
-
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(formData.slug)) {
|
|
300
|
-
setSlugError(
|
|
301
|
-
"Slug must contain only lowercase letters, numbers, and hyphens"
|
|
302
|
-
);
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
if (formData.website && !formData.website.match(/^https?:\/\/.+/)) {
|
|
306
|
-
setError(
|
|
307
|
-
"Website must be a valid URL (starting with http:// or https://)"
|
|
308
|
-
);
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
return true;
|
|
312
|
-
};
|
|
313
|
-
const handleSubmit = async (e) => {
|
|
314
|
-
var _a;
|
|
315
|
-
e.preventDefault();
|
|
316
|
-
if (!validateForm()) return;
|
|
317
|
-
setLoading(true);
|
|
318
|
-
setError(null);
|
|
319
|
-
try {
|
|
320
|
-
const url = isCreating ? "/admin/brands" : `/admin/brands/${brand == null ? void 0 : brand.id}`;
|
|
321
|
-
const method = isCreating ? "POST" : "PUT";
|
|
322
|
-
const response = await sdk.client.fetch(url, {
|
|
323
|
-
method,
|
|
324
|
-
headers: {
|
|
325
|
-
"Content-Type": "application/json"
|
|
326
|
-
},
|
|
327
|
-
body: JSON.stringify(formData)
|
|
328
|
-
});
|
|
329
|
-
const data = await response.json();
|
|
330
|
-
if (response.ok) {
|
|
331
|
-
onClose();
|
|
332
|
-
} else {
|
|
333
|
-
setError(data.error || "Failed to save brand");
|
|
334
|
-
if ((_a = data.error) == null ? void 0 : _a.includes("slug")) {
|
|
335
|
-
setSlugError("This slug is already in use");
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
} catch (err) {
|
|
339
|
-
setError("An error occurred while saving the brand");
|
|
340
|
-
console.error("Error saving brand:", err);
|
|
341
|
-
} finally {
|
|
342
|
-
setLoading(false);
|
|
343
|
-
}
|
|
344
|
-
};
|
|
345
|
-
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-2xl rounded-lg bg-ui-bg-base p-6 max-h-[90vh] overflow-y-auto", children: [
|
|
346
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
347
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
348
|
-
/* @__PURE__ */ jsx(Heading, { level: "h2", children: isCreating ? "Create New Brand" : "Edit Brand" }),
|
|
349
|
-
!isCreating && brand && /* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle", children: [
|
|
350
|
-
"Editing: ",
|
|
351
|
-
brand.name
|
|
352
|
-
] })
|
|
353
|
-
] }),
|
|
354
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsx(X, {}) })
|
|
355
|
-
] }),
|
|
356
|
-
error && /* @__PURE__ */ jsx(Alert, { variant: "error", dismissible: true, className: "mb-4", children: error }),
|
|
357
|
-
/* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
|
|
358
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
359
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "name", className: "mb-2", children: "Brand Name *" }),
|
|
360
|
-
/* @__PURE__ */ jsx(
|
|
361
|
-
Input,
|
|
362
|
-
{
|
|
363
|
-
id: "name",
|
|
364
|
-
placeholder: "e.g., Nike",
|
|
365
|
-
value: formData.name,
|
|
366
|
-
onChange: (e) => handleNameChange(e.target.value),
|
|
367
|
-
required: true
|
|
368
|
-
}
|
|
369
|
-
)
|
|
370
|
-
] }),
|
|
371
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
372
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "slug", className: "mb-2", children: "Slug *" }),
|
|
373
|
-
/* @__PURE__ */ jsx(
|
|
374
|
-
Input,
|
|
375
|
-
{
|
|
376
|
-
id: "slug",
|
|
377
|
-
placeholder: "e.g., nike",
|
|
378
|
-
value: formData.slug,
|
|
379
|
-
onChange: (e) => handleSlugChange(e.target.value),
|
|
380
|
-
required: true
|
|
381
|
-
}
|
|
382
|
-
),
|
|
383
|
-
slugError && /* @__PURE__ */ jsx(Text, { className: "mt-1 text-sm text-ui-fg-error", children: slugError }),
|
|
384
|
-
/* @__PURE__ */ jsx(Text, { className: "mt-1 text-xs text-ui-fg-subtle", children: "Used in URLs and must be unique" })
|
|
385
|
-
] }),
|
|
386
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
387
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "description", className: "mb-2", children: "Description" }),
|
|
388
|
-
/* @__PURE__ */ jsx(
|
|
389
|
-
Textarea,
|
|
390
|
-
{
|
|
391
|
-
id: "description",
|
|
392
|
-
placeholder: "Enter brand description...",
|
|
393
|
-
value: formData.description,
|
|
394
|
-
onChange: (e) => setFormData((prev) => ({
|
|
395
|
-
...prev,
|
|
396
|
-
description: e.target.value
|
|
397
|
-
})),
|
|
398
|
-
rows: 4
|
|
399
|
-
}
|
|
400
|
-
)
|
|
401
|
-
] }),
|
|
402
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
403
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "website", className: "mb-2", children: "Website" }),
|
|
404
|
-
/* @__PURE__ */ jsx(
|
|
405
|
-
Input,
|
|
406
|
-
{
|
|
407
|
-
id: "website",
|
|
408
|
-
type: "url",
|
|
409
|
-
placeholder: "https://example.com",
|
|
410
|
-
value: formData.website,
|
|
411
|
-
onChange: (e) => setFormData((prev) => ({ ...prev, website: e.target.value }))
|
|
412
|
-
}
|
|
413
|
-
),
|
|
414
|
-
/* @__PURE__ */ jsx(Text, { className: "mt-1 text-xs text-ui-fg-subtle", children: "Include the full URL with http:// or https://" })
|
|
415
|
-
] }),
|
|
416
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-lg border p-4", children: [
|
|
417
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
418
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "is_active", className: "font-medium", children: "Active Status" }),
|
|
419
|
-
/* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: "Active brands are visible in your store" })
|
|
420
|
-
] }),
|
|
421
|
-
/* @__PURE__ */ jsx(
|
|
422
|
-
Switch,
|
|
423
|
-
{
|
|
424
|
-
id: "is_active",
|
|
425
|
-
checked: formData.is_active,
|
|
426
|
-
onCheckedChange: (checked) => setFormData((prev) => ({ ...prev, is_active: checked }))
|
|
427
|
-
}
|
|
428
|
-
)
|
|
429
|
-
] }),
|
|
430
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3 pt-4 border-t", children: [
|
|
431
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "secondary", onClick: onClose, children: "Cancel" }),
|
|
432
|
-
/* @__PURE__ */ jsx(Button, { type: "submit", disabled: loading, children: loading ? "Saving..." : isCreating ? "Create Brand" : "Update Brand" })
|
|
433
|
-
] })
|
|
434
|
-
] })
|
|
435
|
-
] }) });
|
|
436
|
-
};
|
|
437
|
-
const BrandImageUploader = ({
|
|
438
|
-
brand,
|
|
439
|
-
imageType,
|
|
440
|
-
onClose
|
|
441
|
-
}) => {
|
|
442
|
-
const currentImage = imageType === "image" ? brand.image : brand.logo;
|
|
443
|
-
const [displayImage, setDisplayImage] = useState(currentImage || null);
|
|
444
|
-
const [isUploading, setIsUploading] = useState(false);
|
|
445
|
-
const [isDragging, setIsDragging] = useState(false);
|
|
446
|
-
const [error, setError] = useState(null);
|
|
447
|
-
const [previewUrl, setPreviewUrl] = useState(null);
|
|
448
|
-
const isLogo = imageType === "logo";
|
|
449
|
-
const maxSize = isLogo ? 2 : 5;
|
|
450
|
-
const allowedTypes = isLogo ? ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"] : ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
451
|
-
const formatFileTypes = () => {
|
|
452
|
-
return isLogo ? "JPEG, PNG, GIF, WebP, or SVG" : "JPEG, PNG, GIF, or WebP";
|
|
453
|
-
};
|
|
454
|
-
const validateFile = (file) => {
|
|
455
|
-
if (!allowedTypes.includes(file.type)) {
|
|
456
|
-
setError(`Please upload a valid image file (${formatFileTypes()})`);
|
|
457
|
-
return false;
|
|
458
|
-
}
|
|
459
|
-
if (file.size > maxSize * 1024 * 1024) {
|
|
460
|
-
setError(`File size must be less than ${maxSize}MB`);
|
|
461
|
-
return false;
|
|
462
|
-
}
|
|
463
|
-
return true;
|
|
464
|
-
};
|
|
465
|
-
const handleFileUpload = async (file) => {
|
|
466
|
-
if (!validateFile(file)) return;
|
|
467
|
-
setError(null);
|
|
468
|
-
const reader = new FileReader();
|
|
469
|
-
reader.onloadend = () => {
|
|
470
|
-
setPreviewUrl(reader.result);
|
|
471
|
-
};
|
|
472
|
-
reader.readAsDataURL(file);
|
|
473
|
-
setIsUploading(true);
|
|
474
|
-
const formData = new FormData();
|
|
475
|
-
formData.append("file", file);
|
|
476
|
-
try {
|
|
477
|
-
const response = await sdk.client.fetch(
|
|
478
|
-
`/admin/brands/${brand.id}/${imageType}`,
|
|
479
|
-
{
|
|
480
|
-
method: "POST",
|
|
481
|
-
body: formData
|
|
482
|
-
}
|
|
483
|
-
);
|
|
484
|
-
if (response.ok) {
|
|
485
|
-
const result = await response.json();
|
|
486
|
-
const newImageUrl = result.brand[imageType];
|
|
487
|
-
setDisplayImage(newImageUrl);
|
|
488
|
-
setPreviewUrl(null);
|
|
489
|
-
setTimeout(() => {
|
|
490
|
-
onClose();
|
|
491
|
-
}, 1e3);
|
|
492
|
-
} else {
|
|
493
|
-
const errorData = await response.json();
|
|
494
|
-
setError(errorData.error || `Failed to upload ${imageType}`);
|
|
495
|
-
setPreviewUrl(null);
|
|
496
|
-
}
|
|
497
|
-
} catch (err) {
|
|
498
|
-
setError(`Error uploading ${imageType}`);
|
|
499
|
-
setPreviewUrl(null);
|
|
500
|
-
console.error(`Error uploading ${imageType}:`, err);
|
|
501
|
-
} finally {
|
|
502
|
-
setIsUploading(false);
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
const handleDelete = async () => {
|
|
506
|
-
if (!confirm(`Are you sure you want to delete the ${imageType}?`)) return;
|
|
507
|
-
setIsUploading(true);
|
|
508
|
-
setError(null);
|
|
509
|
-
try {
|
|
510
|
-
const response = await sdk.client.fetch(
|
|
511
|
-
`/admin/brands/${brand.id}/${imageType}`,
|
|
512
|
-
{
|
|
513
|
-
method: "DELETE"
|
|
514
|
-
}
|
|
515
|
-
);
|
|
516
|
-
if (response.ok) {
|
|
517
|
-
setDisplayImage(null);
|
|
518
|
-
setPreviewUrl(null);
|
|
519
|
-
setTimeout(() => {
|
|
520
|
-
onClose();
|
|
521
|
-
}, 500);
|
|
522
|
-
} else {
|
|
523
|
-
const errorData = await response.json();
|
|
524
|
-
setError(errorData.error || `Failed to delete ${imageType}`);
|
|
525
|
-
}
|
|
526
|
-
} catch (err) {
|
|
527
|
-
setError(`Error deleting ${imageType}`);
|
|
528
|
-
console.error(`Error deleting ${imageType}:`, err);
|
|
529
|
-
} finally {
|
|
530
|
-
setIsUploading(false);
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
const handleDragEnter = useCallback((e) => {
|
|
534
|
-
e.preventDefault();
|
|
535
|
-
e.stopPropagation();
|
|
536
|
-
setIsDragging(true);
|
|
537
|
-
}, []);
|
|
538
|
-
const handleDragLeave = useCallback((e) => {
|
|
539
|
-
e.preventDefault();
|
|
540
|
-
e.stopPropagation();
|
|
541
|
-
setIsDragging(false);
|
|
542
|
-
}, []);
|
|
543
|
-
const handleDragOver = useCallback((e) => {
|
|
544
|
-
e.preventDefault();
|
|
545
|
-
e.stopPropagation();
|
|
546
|
-
}, []);
|
|
547
|
-
const handleDrop = useCallback((e) => {
|
|
548
|
-
var _a;
|
|
549
|
-
e.preventDefault();
|
|
550
|
-
e.stopPropagation();
|
|
551
|
-
setIsDragging(false);
|
|
552
|
-
const file = (_a = e.dataTransfer.files) == null ? void 0 : _a[0];
|
|
553
|
-
if (file) {
|
|
554
|
-
handleFileUpload(file);
|
|
555
|
-
}
|
|
556
|
-
}, []);
|
|
557
|
-
const imageToDisplay = previewUrl || displayImage;
|
|
558
|
-
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-2xl rounded-lg bg-ui-bg-base p-6", children: [
|
|
559
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
560
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
561
|
-
/* @__PURE__ */ jsxs(Heading, { level: "h2", children: [
|
|
562
|
-
"Upload Brand ",
|
|
563
|
-
isLogo ? "Logo" : "Image"
|
|
564
|
-
] }),
|
|
565
|
-
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: brand.name })
|
|
566
|
-
] }),
|
|
567
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsx(X, {}) })
|
|
568
|
-
] }),
|
|
569
|
-
error && /* @__PURE__ */ jsx(Alert, { variant: "error", dismissible: true, className: "mb-4", children: error }),
|
|
570
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
571
|
-
imageToDisplay ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
572
|
-
/* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-subtle", children: [
|
|
573
|
-
/* @__PURE__ */ jsx(
|
|
574
|
-
"img",
|
|
575
|
-
{
|
|
576
|
-
src: imageToDisplay,
|
|
577
|
-
alt: `Brand ${imageType}`,
|
|
578
|
-
className: clx(
|
|
579
|
-
"w-full object-contain",
|
|
580
|
-
isLogo ? "h-32" : "h-64"
|
|
581
|
-
)
|
|
582
|
-
}
|
|
583
|
-
),
|
|
584
|
-
isUploading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsx("div", { className: "text-white", children: "Uploading..." }) })
|
|
585
|
-
] }),
|
|
586
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
587
|
-
/* @__PURE__ */ jsxs(
|
|
588
|
-
Button,
|
|
589
|
-
{
|
|
590
|
-
variant: "secondary",
|
|
591
|
-
disabled: isUploading,
|
|
592
|
-
onClick: () => {
|
|
593
|
-
var _a;
|
|
594
|
-
return (_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click();
|
|
595
|
-
},
|
|
596
|
-
children: [
|
|
597
|
-
/* @__PURE__ */ jsx(CloudArrowUp, { className: "mr-2" }),
|
|
598
|
-
"Replace"
|
|
599
|
-
]
|
|
600
|
-
}
|
|
601
|
-
),
|
|
602
|
-
/* @__PURE__ */ jsxs(
|
|
603
|
-
Button,
|
|
604
|
-
{
|
|
605
|
-
variant: "danger",
|
|
606
|
-
disabled: isUploading,
|
|
607
|
-
onClick: handleDelete,
|
|
608
|
-
children: [
|
|
609
|
-
/* @__PURE__ */ jsx(Trash, { className: "mr-2" }),
|
|
610
|
-
"Delete"
|
|
611
|
-
]
|
|
612
|
-
}
|
|
613
|
-
)
|
|
614
|
-
] })
|
|
615
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
616
|
-
"div",
|
|
617
|
-
{
|
|
618
|
-
className: clx(
|
|
619
|
-
"flex flex-col items-center justify-center rounded-lg border-2 border-dashed transition-colors",
|
|
620
|
-
isLogo ? "h-48" : "h-64",
|
|
621
|
-
isDragging ? "border-ui-border-interactive bg-ui-bg-highlight" : "border-ui-border-base bg-ui-bg-subtle",
|
|
622
|
-
!isUploading && "cursor-pointer"
|
|
623
|
-
),
|
|
624
|
-
onDragEnter: handleDragEnter,
|
|
625
|
-
onDragLeave: handleDragLeave,
|
|
626
|
-
onDragOver: handleDragOver,
|
|
627
|
-
onDrop: handleDrop,
|
|
628
|
-
onClick: () => {
|
|
629
|
-
var _a;
|
|
630
|
-
return !isUploading && ((_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click());
|
|
631
|
-
},
|
|
632
|
-
children: [
|
|
633
|
-
isLogo ? /* @__PURE__ */ jsx(BuildingStorefront, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }) : /* @__PURE__ */ jsx(PhotoSolid, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }),
|
|
634
|
-
/* @__PURE__ */ jsx(Text, { className: "mb-2 text-lg font-medium", children: isDragging ? `Drop ${imageType} here` : `Upload ${isLogo ? "Logo" : "Image"}` }),
|
|
635
|
-
/* @__PURE__ */ jsx(Text, { className: "mb-4 text-center text-ui-fg-subtle", children: "Drag and drop an image here, or click to select" }),
|
|
636
|
-
/* @__PURE__ */ jsxs(Text, { className: "text-sm text-ui-fg-subtle", children: [
|
|
637
|
-
formatFileTypes(),
|
|
638
|
-
" • Max ",
|
|
639
|
-
maxSize,
|
|
640
|
-
"MB"
|
|
641
|
-
] }),
|
|
642
|
-
isLogo && /* @__PURE__ */ jsx(Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: Square image, minimum 200x200px" }),
|
|
643
|
-
isUploading && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(Text, { children: "Uploading..." }) })
|
|
644
|
-
]
|
|
645
|
-
}
|
|
646
|
-
),
|
|
647
|
-
/* @__PURE__ */ jsx(
|
|
648
|
-
"input",
|
|
649
|
-
{
|
|
650
|
-
id: `${imageType}-file-input`,
|
|
651
|
-
type: "file",
|
|
652
|
-
accept: allowedTypes.join(","),
|
|
653
|
-
onChange: (e) => {
|
|
654
|
-
var _a;
|
|
655
|
-
const file = (_a = e.target.files) == null ? void 0 : _a[0];
|
|
656
|
-
if (file) handleFileUpload(file);
|
|
657
|
-
e.target.value = "";
|
|
658
|
-
},
|
|
659
|
-
className: "hidden"
|
|
660
|
-
}
|
|
661
|
-
)
|
|
662
|
-
] }),
|
|
663
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-between border-t pt-4", children: [
|
|
664
|
-
/* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: isLogo ? "Logo will be displayed in brand lists and product pages" : "Image can be used for brand pages and marketing" }),
|
|
665
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: "Close" })
|
|
666
|
-
] })
|
|
667
|
-
] }) });
|
|
668
|
-
};
|
|
669
|
-
const BrandsPage = () => {
|
|
670
|
-
const [brands, setBrands] = useState([]);
|
|
671
|
-
const [loading, setLoading] = useState(true);
|
|
672
|
-
const [error, setError] = useState(null);
|
|
673
|
-
const [selectedBrand, setSelectedBrand] = useState(null);
|
|
674
|
-
const [showForm, setShowForm] = useState(false);
|
|
675
|
-
const [showImageUploader, setShowImageUploader] = useState(false);
|
|
676
|
-
const [imageType, setImageType] = useState("image");
|
|
677
|
-
const [searchQuery, setSearchQuery] = useState("");
|
|
678
|
-
const [isCreating, setIsCreating] = useState(false);
|
|
679
|
-
useEffect(() => {
|
|
680
|
-
fetchBrands();
|
|
681
|
-
}, []);
|
|
682
|
-
const fetchBrands = async () => {
|
|
683
|
-
setLoading(true);
|
|
684
|
-
setError(null);
|
|
685
|
-
try {
|
|
686
|
-
const queryParams = new URLSearchParams({ limit: "100" });
|
|
687
|
-
if (searchQuery) {
|
|
688
|
-
queryParams.append("q", searchQuery);
|
|
689
|
-
}
|
|
690
|
-
const response = await sdk.client.fetch(
|
|
691
|
-
`/admin/brands?${queryParams}`,
|
|
692
|
-
{}
|
|
693
|
-
);
|
|
694
|
-
if (response.ok) {
|
|
695
|
-
const data = await response.json();
|
|
696
|
-
setBrands(data.brands || []);
|
|
697
|
-
} else {
|
|
698
|
-
setError("Failed to fetch brands");
|
|
699
|
-
}
|
|
700
|
-
} catch (err) {
|
|
701
|
-
if (err instanceof FetchError && err.status === 404) {
|
|
702
|
-
setBrands([]);
|
|
703
|
-
setError(null);
|
|
704
|
-
} else {
|
|
705
|
-
setError("Error fetching brands");
|
|
706
|
-
console.error("Error fetching brands:", err);
|
|
707
|
-
}
|
|
708
|
-
} finally {
|
|
709
|
-
setLoading(false);
|
|
710
|
-
}
|
|
711
|
-
};
|
|
712
|
-
const handleCreateBrand = () => {
|
|
713
|
-
setIsCreating(true);
|
|
714
|
-
setSelectedBrand(null);
|
|
715
|
-
setShowForm(true);
|
|
716
|
-
};
|
|
717
|
-
const handleEditBrand = (brand) => {
|
|
718
|
-
setIsCreating(false);
|
|
719
|
-
setSelectedBrand(brand);
|
|
720
|
-
setShowForm(true);
|
|
721
|
-
};
|
|
722
|
-
const handleDeleteBrand = async (brandId) => {
|
|
723
|
-
if (!confirm(
|
|
724
|
-
"Are you sure you want to delete this brand? This action cannot be undone."
|
|
725
|
-
)) {
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
try {
|
|
729
|
-
const response = await sdk.client.fetch(`/admin/brands/${brandId}`, {
|
|
730
|
-
method: "DELETE"
|
|
731
|
-
});
|
|
732
|
-
if (response.ok) {
|
|
733
|
-
fetchBrands();
|
|
734
|
-
} else {
|
|
735
|
-
alert("Failed to delete brand");
|
|
736
|
-
}
|
|
737
|
-
} catch (err) {
|
|
738
|
-
alert("Error deleting brand");
|
|
739
|
-
console.error("Error deleting brand:", err);
|
|
740
|
-
}
|
|
741
|
-
};
|
|
742
|
-
const handleToggleActive = async (brand) => {
|
|
743
|
-
try {
|
|
744
|
-
const response = await sdk.client.fetch(`/admin/brands/${brand.id}`, {
|
|
745
|
-
method: "PUT",
|
|
746
|
-
headers: {
|
|
747
|
-
"Content-Type": "application/json"
|
|
748
|
-
},
|
|
749
|
-
body: JSON.stringify({
|
|
750
|
-
is_active: !brand.is_active
|
|
751
|
-
})
|
|
752
|
-
});
|
|
753
|
-
if (response.ok) {
|
|
754
|
-
fetchBrands();
|
|
755
|
-
} else {
|
|
756
|
-
alert("Failed to update brand status");
|
|
757
|
-
}
|
|
758
|
-
} catch (err) {
|
|
759
|
-
alert("Error updating brand status");
|
|
760
|
-
console.error("Error updating brand status:", err);
|
|
761
|
-
}
|
|
762
|
-
};
|
|
763
|
-
const handleImageUpload = (brand, type) => {
|
|
764
|
-
setSelectedBrand(brand);
|
|
765
|
-
setImageType(type);
|
|
766
|
-
setShowImageUploader(true);
|
|
767
|
-
};
|
|
768
|
-
const handleFormClose = () => {
|
|
769
|
-
setShowForm(false);
|
|
770
|
-
setSelectedBrand(null);
|
|
771
|
-
setIsCreating(false);
|
|
772
|
-
fetchBrands();
|
|
773
|
-
};
|
|
774
|
-
const handleImageUploaderClose = () => {
|
|
775
|
-
setShowImageUploader(false);
|
|
776
|
-
setSelectedBrand(null);
|
|
777
|
-
fetchBrands();
|
|
778
|
-
};
|
|
779
|
-
const filteredBrands = brands.filter((brand) => {
|
|
780
|
-
var _a;
|
|
781
|
-
if (!searchQuery) return true;
|
|
782
|
-
const query = searchQuery.toLowerCase();
|
|
783
|
-
return brand.name.toLowerCase().includes(query) || brand.slug.toLowerCase().includes(query) || ((_a = brand.description) == null ? void 0 : _a.toLowerCase().includes(query));
|
|
784
|
-
});
|
|
785
|
-
if (loading) {
|
|
786
|
-
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { className: "flex h-64 items-center justify-center", children: /* @__PURE__ */ jsx(Text, { children: "Loading brands..." }) }) });
|
|
787
|
-
}
|
|
788
|
-
if (error) {
|
|
789
|
-
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx(Alert, { variant: "error", dismissible: true, children: error }) });
|
|
790
|
-
}
|
|
791
|
-
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
792
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-8", children: [
|
|
793
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-4", children: [
|
|
794
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
795
|
-
/* @__PURE__ */ jsx(Heading, { level: "h1", className: "mb-2", children: "Brands" }),
|
|
796
|
-
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Manage your product brands and their images" })
|
|
797
|
-
] }),
|
|
798
|
-
/* @__PURE__ */ jsxs(Button, { onClick: handleCreateBrand, children: [
|
|
799
|
-
/* @__PURE__ */ jsx(Plus, { className: "mr-2" }),
|
|
800
|
-
"Create Brand"
|
|
801
|
-
] })
|
|
802
|
-
] }),
|
|
803
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsxs("div", { className: "relative flex-1 max-w-md", children: [
|
|
804
|
-
/* @__PURE__ */ jsx(
|
|
805
|
-
Input,
|
|
806
|
-
{
|
|
807
|
-
placeholder: "Search brands...",
|
|
808
|
-
value: searchQuery,
|
|
809
|
-
onChange: (e) => setSearchQuery(e.target.value),
|
|
810
|
-
className: "pl-10"
|
|
811
|
-
}
|
|
812
|
-
),
|
|
813
|
-
/* @__PURE__ */ jsx(MagnifyingGlass, { className: "absolute left-3 top-1/2 -translate-y-1/2 text-ui-fg-muted" })
|
|
814
|
-
] }) })
|
|
815
|
-
] }),
|
|
816
|
-
filteredBrands.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex h-64 flex-col items-center justify-center rounded-lg border-2 border-dashed border-ui-border-base", children: [
|
|
817
|
-
/* @__PURE__ */ jsx(BuildingStorefront, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }),
|
|
818
|
-
/* @__PURE__ */ jsx(Text, { className: "mb-2 text-lg font-medium", children: searchQuery ? "No brands found" : "No brands yet" }),
|
|
819
|
-
/* @__PURE__ */ jsx(Text, { className: "mb-4 text-ui-fg-subtle", children: searchQuery ? "Try adjusting your search query" : "Create your first brand to get started" }),
|
|
820
|
-
!searchQuery && /* @__PURE__ */ jsxs(Button, { onClick: handleCreateBrand, children: [
|
|
821
|
-
/* @__PURE__ */ jsx(Plus, { className: "mr-2" }),
|
|
822
|
-
"Create Brand"
|
|
823
|
-
] })
|
|
824
|
-
] }) : /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-lg border", children: /* @__PURE__ */ jsxs(Table, { children: [
|
|
825
|
-
/* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
826
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Logo" }),
|
|
827
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Name" }),
|
|
828
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Slug" }),
|
|
829
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Status" }),
|
|
830
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Images" }),
|
|
831
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "Actions" })
|
|
832
|
-
] }) }),
|
|
833
|
-
/* @__PURE__ */ jsx(Table.Body, { children: filteredBrands.map((brand) => /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
834
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: brand.logo ? /* @__PURE__ */ jsx(
|
|
835
|
-
"img",
|
|
836
|
-
{
|
|
837
|
-
src: brand.logo,
|
|
838
|
-
alt: `${brand.name} logo`,
|
|
839
|
-
className: "h-10 w-10 object-contain rounded"
|
|
840
|
-
}
|
|
841
|
-
) : /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded bg-ui-bg-subtle", children: /* @__PURE__ */ jsx(BuildingStorefront, { className: "h-5 w-5 text-ui-fg-subtle" }) }) }),
|
|
842
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs("div", { children: [
|
|
843
|
-
/* @__PURE__ */ jsx(Text, { className: "font-medium", children: brand.name }),
|
|
844
|
-
brand.website && /* @__PURE__ */ jsx(Text, { className: "text-xs text-ui-fg-subtle", children: brand.website })
|
|
845
|
-
] }) }),
|
|
846
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: "blue", children: brand.slug }) }),
|
|
847
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: brand.is_active ? "green" : "grey", children: brand.is_active ? "Active" : "Inactive" }) }),
|
|
848
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
849
|
-
brand.image && /* @__PURE__ */ jsxs(Badge, { color: "green", size: "xsmall", children: [
|
|
850
|
-
/* @__PURE__ */ jsx(PhotoSolid, { className: "mr-1" }),
|
|
851
|
-
"Image"
|
|
852
|
-
] }),
|
|
853
|
-
brand.logo && /* @__PURE__ */ jsxs(Badge, { color: "green", size: "xsmall", children: [
|
|
854
|
-
/* @__PURE__ */ jsx(BuildingStorefront, { className: "mr-1" }),
|
|
855
|
-
"Logo"
|
|
856
|
-
] }),
|
|
857
|
-
!brand.image && !brand.logo && /* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-muted", children: "No images" })
|
|
858
|
-
] }) }),
|
|
859
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-2", children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
860
|
-
/* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { variant: "transparent", size: "small", children: /* @__PURE__ */ jsx(EllipsisHorizontal, {}) }) }),
|
|
861
|
-
/* @__PURE__ */ jsxs(DropdownMenu.Content, { children: [
|
|
862
|
-
/* @__PURE__ */ jsxs(
|
|
863
|
-
DropdownMenu.Item,
|
|
864
|
-
{
|
|
865
|
-
onClick: () => handleEditBrand(brand),
|
|
866
|
-
children: [
|
|
867
|
-
/* @__PURE__ */ jsx(PencilSquare, { className: "mr-2" }),
|
|
868
|
-
"Edit Details"
|
|
869
|
-
]
|
|
870
|
-
}
|
|
871
|
-
),
|
|
872
|
-
/* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
|
|
873
|
-
/* @__PURE__ */ jsxs(
|
|
874
|
-
DropdownMenu.Item,
|
|
875
|
-
{
|
|
876
|
-
onClick: () => handleImageUpload(brand, "image"),
|
|
877
|
-
children: [
|
|
878
|
-
/* @__PURE__ */ jsx(CloudArrowUp, { className: "mr-2" }),
|
|
879
|
-
brand.image ? "Replace Image" : "Upload Image"
|
|
880
|
-
]
|
|
881
|
-
}
|
|
882
|
-
),
|
|
883
|
-
/* @__PURE__ */ jsxs(
|
|
884
|
-
DropdownMenu.Item,
|
|
885
|
-
{
|
|
886
|
-
onClick: () => handleImageUpload(brand, "logo"),
|
|
887
|
-
children: [
|
|
888
|
-
/* @__PURE__ */ jsx(CloudArrowUp, { className: "mr-2" }),
|
|
889
|
-
brand.logo ? "Replace Logo" : "Upload Logo"
|
|
890
|
-
]
|
|
891
|
-
}
|
|
892
|
-
),
|
|
893
|
-
/* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
|
|
894
|
-
/* @__PURE__ */ jsxs(
|
|
895
|
-
DropdownMenu.Item,
|
|
896
|
-
{
|
|
897
|
-
onClick: () => handleToggleActive(brand),
|
|
898
|
-
children: [
|
|
899
|
-
/* @__PURE__ */ jsx(
|
|
900
|
-
Switch,
|
|
901
|
-
{
|
|
902
|
-
checked: brand.is_active,
|
|
903
|
-
className: "mr-2 pointer-events-none"
|
|
904
|
-
}
|
|
905
|
-
),
|
|
906
|
-
brand.is_active ? "Deactivate" : "Activate"
|
|
907
|
-
]
|
|
908
|
-
}
|
|
909
|
-
),
|
|
910
|
-
/* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
|
|
911
|
-
/* @__PURE__ */ jsxs(
|
|
912
|
-
DropdownMenu.Item,
|
|
913
|
-
{
|
|
914
|
-
onClick: () => handleDeleteBrand(brand.id),
|
|
915
|
-
className: "text-ui-fg-error",
|
|
916
|
-
children: [
|
|
917
|
-
/* @__PURE__ */ jsx(Trash, { className: "mr-2" }),
|
|
918
|
-
"Delete Brand"
|
|
919
|
-
]
|
|
920
|
-
}
|
|
921
|
-
)
|
|
922
|
-
] })
|
|
923
|
-
] }) }) })
|
|
924
|
-
] }, brand.id)) })
|
|
925
|
-
] }) }),
|
|
926
|
-
showForm && /* @__PURE__ */ jsx(
|
|
927
|
-
BrandForm,
|
|
928
|
-
{
|
|
929
|
-
brand: selectedBrand,
|
|
930
|
-
isCreating,
|
|
931
|
-
onClose: handleFormClose
|
|
932
|
-
}
|
|
933
|
-
),
|
|
934
|
-
showImageUploader && selectedBrand && /* @__PURE__ */ jsx(
|
|
935
|
-
BrandImageUploader,
|
|
936
|
-
{
|
|
937
|
-
brand: selectedBrand,
|
|
938
|
-
imageType,
|
|
939
|
-
onClose: handleImageUploaderClose
|
|
940
|
-
}
|
|
941
|
-
)
|
|
942
|
-
] });
|
|
943
|
-
};
|
|
944
|
-
const config = defineRouteConfig({
|
|
945
|
-
label: "Brands",
|
|
946
|
-
icon: BuildingStorefront
|
|
947
|
-
});
|
|
948
|
-
const widgetModule = { widgets: [
|
|
949
|
-
{
|
|
950
|
-
Component: ProductBrandWidget,
|
|
951
|
-
zone: ["product.details.side.after"]
|
|
952
|
-
}
|
|
953
|
-
] };
|
|
954
|
-
const routeModule = {
|
|
955
|
-
routes: [
|
|
956
|
-
{
|
|
957
|
-
Component: BrandsPage,
|
|
958
|
-
path: "/brands"
|
|
959
|
-
}
|
|
960
|
-
]
|
|
961
|
-
};
|
|
962
|
-
const menuItemModule = {
|
|
963
|
-
menuItems: [
|
|
964
|
-
{
|
|
965
|
-
label: config.label,
|
|
966
|
-
icon: config.icon,
|
|
967
|
-
path: "/brands",
|
|
968
|
-
nested: void 0
|
|
969
|
-
}
|
|
970
|
-
]
|
|
971
|
-
};
|
|
972
|
-
const formModule = { customFields: {} };
|
|
973
|
-
const displayModule = {
|
|
974
|
-
displays: {}
|
|
975
|
-
};
|
|
976
|
-
const i18nModule = { resources: {} };
|
|
977
|
-
const plugin = {
|
|
978
|
-
widgetModule,
|
|
979
|
-
routeModule,
|
|
980
|
-
menuItemModule,
|
|
981
|
-
formModule,
|
|
982
|
-
displayModule,
|
|
983
|
-
i18nModule
|
|
984
|
-
};
|
|
985
|
-
export {
|
|
986
|
-
plugin as default
|
|
987
|
-
};
|