@froze-labs/medusa-plugin-brands 0.0.1

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.
@@ -0,0 +1,482 @@
1
+ "use strict";
2
+ const jsxRuntime = require("react/jsx-runtime");
3
+ const adminSdk = require("@medusajs/admin-sdk");
4
+ const icons = require("@medusajs/icons");
5
+ const ui = require("@medusajs/ui");
6
+ const reactQuery = require("@tanstack/react-query");
7
+ const react = require("react");
8
+ const reactRouterDom = require("react-router-dom");
9
+ const Medusa = require("@medusajs/js-sdk");
10
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
11
+ const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
12
+ const sdk = new Medusa__default.default({
13
+ baseUrl: "",
14
+ auth: {
15
+ type: "session"
16
+ }
17
+ });
18
+ const BrandsPage = () => {
19
+ var _a;
20
+ const navigate = reactRouterDom.useNavigate();
21
+ const [currentPage, setCurrentPage] = react.useState(0);
22
+ const pageSize = 20;
23
+ const offset = react.useMemo(() => currentPage * pageSize, [currentPage]);
24
+ const { data, isLoading } = reactQuery.useQuery({
25
+ queryKey: ["brands", pageSize, offset],
26
+ queryFn: () => sdk.client.fetch("/admin/brands", {
27
+ query: {
28
+ limit: pageSize,
29
+ offset
30
+ }
31
+ })
32
+ });
33
+ const pageCount = react.useMemo(
34
+ () => Math.ceil(((data == null ? void 0 : data.count) || 0) / pageSize),
35
+ [data == null ? void 0 : data.count]
36
+ );
37
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
38
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
39
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Brands" }),
40
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => navigate("/brands/create"), children: "Create Brand" })
41
+ ] }),
42
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Loading..." }) : !((_a = data == null ? void 0 : data.brands) == null ? void 0 : _a.length) ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "No brands found" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
43
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
44
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
45
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Name" }),
46
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Slug" }),
47
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Products" }),
48
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Created" })
49
+ ] }) }),
50
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: data.brands.map((brand) => /* @__PURE__ */ jsxRuntime.jsxs(
51
+ ui.Table.Row,
52
+ {
53
+ className: "cursor-pointer",
54
+ onClick: () => navigate(`/brands/${brand.id}`),
55
+ children: [
56
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
57
+ brand.logo_image && /* @__PURE__ */ jsxRuntime.jsx(
58
+ "img",
59
+ {
60
+ src: brand.logo_image,
61
+ alt: brand.name,
62
+ className: "h-8 w-8 rounded object-cover"
63
+ }
64
+ ),
65
+ /* @__PURE__ */ jsxRuntime.jsx(
66
+ reactRouterDom.Link,
67
+ {
68
+ to: `/brands/${brand.id}`,
69
+ className: "text-ui-fg-interactive hover:underline",
70
+ onClick: (e) => e.stopPropagation(),
71
+ children: brand.name
72
+ }
73
+ )
74
+ ] }) }),
75
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: brand.slug }),
76
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: brand.product_count }),
77
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: new Date(brand.created_at).toLocaleDateString() })
78
+ ]
79
+ },
80
+ brand.id
81
+ )) })
82
+ ] }),
83
+ pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mt-4", children: [
84
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted", children: [
85
+ "Page ",
86
+ currentPage + 1,
87
+ " of ",
88
+ pageCount
89
+ ] }),
90
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
91
+ /* @__PURE__ */ jsxRuntime.jsx(
92
+ ui.Button,
93
+ {
94
+ variant: "secondary",
95
+ size: "small",
96
+ disabled: currentPage === 0,
97
+ onClick: () => setCurrentPage((p) => p - 1),
98
+ children: "Previous"
99
+ }
100
+ ),
101
+ /* @__PURE__ */ jsxRuntime.jsx(
102
+ ui.Button,
103
+ {
104
+ variant: "secondary",
105
+ size: "small",
106
+ disabled: currentPage >= pageCount - 1,
107
+ onClick: () => setCurrentPage((p) => p + 1),
108
+ children: "Next"
109
+ }
110
+ )
111
+ ] })
112
+ ] })
113
+ ] }) })
114
+ ] });
115
+ };
116
+ const config = adminSdk.defineRouteConfig({
117
+ label: "Brands",
118
+ icon: icons.TagSolid
119
+ });
120
+ const BrandDetailPage = () => {
121
+ var _a, _b;
122
+ const { id } = reactRouterDom.useParams();
123
+ const navigate = reactRouterDom.useNavigate();
124
+ const queryClient = reactQuery.useQueryClient();
125
+ const { data, isLoading } = reactQuery.useQuery({
126
+ queryKey: ["brand", id],
127
+ queryFn: () => sdk.client.fetch(`/admin/brands/${id}`)
128
+ });
129
+ const deleteMutation = reactQuery.useMutation({
130
+ mutationFn: () => sdk.client.fetch(`/admin/brands/${id}`, { method: "DELETE" }),
131
+ onSuccess: () => {
132
+ queryClient.invalidateQueries({ queryKey: ["brands"] });
133
+ ui.toast.success("Success", {
134
+ description: "Brand deleted successfully"
135
+ });
136
+ navigate("/brands");
137
+ },
138
+ onError: () => {
139
+ ui.toast.error("Error", {
140
+ description: "Failed to delete brand"
141
+ });
142
+ }
143
+ });
144
+ if (isLoading) {
145
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Loading..." }) });
146
+ }
147
+ const brand = data == null ? void 0 : data.brand;
148
+ if (!brand) {
149
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Brand not found" }) });
150
+ }
151
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
152
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
153
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
154
+ brand.logo_image && /* @__PURE__ */ jsxRuntime.jsx(
155
+ "img",
156
+ {
157
+ src: brand.logo_image,
158
+ alt: brand.name,
159
+ className: "h-12 w-12 rounded object-cover"
160
+ }
161
+ ),
162
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
163
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: brand.name }),
164
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted", children: [
165
+ "/",
166
+ brand.slug
167
+ ] })
168
+ ] })
169
+ ] }),
170
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
171
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => navigate(`/brands/${id}/edit`), children: "Edit" }),
172
+ /* @__PURE__ */ jsxRuntime.jsx(
173
+ ui.Button,
174
+ {
175
+ variant: "danger",
176
+ onClick: () => {
177
+ if (confirm("Are you sure you want to delete this brand?")) {
178
+ deleteMutation.mutate();
179
+ }
180
+ },
181
+ children: "Delete"
182
+ }
183
+ )
184
+ ] })
185
+ ] }),
186
+ brand.description && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
187
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-2", children: "Description" }),
188
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: brand.description })
189
+ ] }),
190
+ brand.cover_image && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
191
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", className: "mb-2", children: "Cover Image" }),
192
+ /* @__PURE__ */ jsxRuntime.jsx(
193
+ "img",
194
+ {
195
+ src: brand.cover_image,
196
+ alt: `${brand.name} cover`,
197
+ className: "max-h-48 rounded object-cover"
198
+ }
199
+ )
200
+ ] }),
201
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
202
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Heading, { level: "h2", className: "mb-2", children: [
203
+ "Products ",
204
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { children: ((_a = brand.products) == null ? void 0 : _a.length) || 0 })
205
+ ] }),
206
+ ((_b = brand.products) == null ? void 0 : _b.length) > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "list-disc pl-4", children: brand.products.map((product) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
207
+ reactRouterDom.Link,
208
+ {
209
+ to: `/products/${product.id}`,
210
+ className: "text-ui-fg-interactive hover:underline",
211
+ children: product.title
212
+ }
213
+ ) }, product.id)) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-muted", children: "No products linked to this brand" })
214
+ ] })
215
+ ] });
216
+ };
217
+ const CreateBrandPage = () => {
218
+ const navigate = reactRouterDom.useNavigate();
219
+ const [loading, setLoading] = react.useState(false);
220
+ const [form, setForm] = react.useState({
221
+ name: "",
222
+ description: "",
223
+ cover_image: "",
224
+ logo_image: ""
225
+ });
226
+ const handleSubmit = async (e) => {
227
+ e.preventDefault();
228
+ setLoading(true);
229
+ try {
230
+ await sdk.client.fetch("/admin/brands", {
231
+ method: "POST",
232
+ body: {
233
+ name: form.name,
234
+ description: form.description || void 0,
235
+ cover_image: form.cover_image || void 0,
236
+ logo_image: form.logo_image || void 0
237
+ }
238
+ });
239
+ ui.toast.success("Success", {
240
+ description: "Brand created successfully"
241
+ });
242
+ navigate("/brands");
243
+ } catch (error) {
244
+ ui.toast.error("Error", {
245
+ description: "Failed to create brand"
246
+ });
247
+ } finally {
248
+ setLoading(false);
249
+ }
250
+ };
251
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
252
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Create Brand" }) }),
253
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4 px-6 py-4", children: [
254
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
255
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "name", children: "Name *" }),
256
+ /* @__PURE__ */ jsxRuntime.jsx(
257
+ ui.Input,
258
+ {
259
+ id: "name",
260
+ value: form.name,
261
+ onChange: (e) => setForm({ ...form, name: e.target.value }),
262
+ required: true
263
+ }
264
+ )
265
+ ] }),
266
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
267
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "description", children: "Description" }),
268
+ /* @__PURE__ */ jsxRuntime.jsx(
269
+ ui.Textarea,
270
+ {
271
+ id: "description",
272
+ value: form.description,
273
+ onChange: (e) => setForm({ ...form, description: e.target.value })
274
+ }
275
+ )
276
+ ] }),
277
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
278
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "logo_image", children: "Logo Image URL" }),
279
+ /* @__PURE__ */ jsxRuntime.jsx(
280
+ ui.Input,
281
+ {
282
+ id: "logo_image",
283
+ type: "url",
284
+ value: form.logo_image,
285
+ onChange: (e) => setForm({ ...form, logo_image: e.target.value }),
286
+ placeholder: "https://example.com/logo.png"
287
+ }
288
+ )
289
+ ] }),
290
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
291
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "cover_image", children: "Cover Image URL" }),
292
+ /* @__PURE__ */ jsxRuntime.jsx(
293
+ ui.Input,
294
+ {
295
+ id: "cover_image",
296
+ type: "url",
297
+ value: form.cover_image,
298
+ onChange: (e) => setForm({ ...form, cover_image: e.target.value }),
299
+ placeholder: "https://example.com/cover.jpg"
300
+ }
301
+ )
302
+ ] }),
303
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
304
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", isLoading: loading, children: "Create Brand" }),
305
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/brands"), children: "Cancel" })
306
+ ] })
307
+ ] })
308
+ ] });
309
+ };
310
+ const EditBrandPage = () => {
311
+ const { id } = reactRouterDom.useParams();
312
+ const navigate = reactRouterDom.useNavigate();
313
+ const queryClient = reactQuery.useQueryClient();
314
+ const [form, setForm] = react.useState({
315
+ name: "",
316
+ slug: "",
317
+ description: "",
318
+ cover_image: "",
319
+ logo_image: ""
320
+ });
321
+ const { data, isLoading } = reactQuery.useQuery({
322
+ queryKey: ["brand", id],
323
+ queryFn: () => sdk.client.fetch(`/admin/brands/${id}`)
324
+ });
325
+ react.useEffect(() => {
326
+ if (data == null ? void 0 : data.brand) {
327
+ setForm({
328
+ name: data.brand.name,
329
+ slug: data.brand.slug,
330
+ description: data.brand.description || "",
331
+ cover_image: data.brand.cover_image || "",
332
+ logo_image: data.brand.logo_image || ""
333
+ });
334
+ }
335
+ }, [data]);
336
+ const updateMutation = reactQuery.useMutation({
337
+ mutationFn: (formData) => sdk.client.fetch(`/admin/brands/${id}`, {
338
+ method: "PUT",
339
+ body: {
340
+ name: formData.name,
341
+ slug: formData.slug || void 0,
342
+ description: formData.description || null,
343
+ cover_image: formData.cover_image || null,
344
+ logo_image: formData.logo_image || null
345
+ }
346
+ }),
347
+ onSuccess: () => {
348
+ queryClient.invalidateQueries({ queryKey: ["brand", id] });
349
+ queryClient.invalidateQueries({ queryKey: ["brands"] });
350
+ ui.toast.success("Success", {
351
+ description: "Brand updated successfully"
352
+ });
353
+ navigate(`/brands/${id}`);
354
+ },
355
+ onError: () => {
356
+ ui.toast.error("Error", {
357
+ description: "Failed to update brand"
358
+ });
359
+ }
360
+ });
361
+ const handleSubmit = (e) => {
362
+ e.preventDefault();
363
+ updateMutation.mutate(form);
364
+ };
365
+ if (isLoading) {
366
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-6", children: "Loading..." });
367
+ }
368
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
369
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Edit Brand" }) }),
370
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4 px-6 py-4", children: [
371
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
372
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "name", children: "Name *" }),
373
+ /* @__PURE__ */ jsxRuntime.jsx(
374
+ ui.Input,
375
+ {
376
+ id: "name",
377
+ value: form.name,
378
+ onChange: (e) => setForm({ ...form, name: e.target.value }),
379
+ required: true
380
+ }
381
+ )
382
+ ] }),
383
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
384
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "slug", children: "Slug" }),
385
+ /* @__PURE__ */ jsxRuntime.jsx(
386
+ ui.Input,
387
+ {
388
+ id: "slug",
389
+ value: form.slug,
390
+ onChange: (e) => setForm({ ...form, slug: e.target.value })
391
+ }
392
+ )
393
+ ] }),
394
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
395
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "description", children: "Description" }),
396
+ /* @__PURE__ */ jsxRuntime.jsx(
397
+ ui.Textarea,
398
+ {
399
+ id: "description",
400
+ value: form.description,
401
+ onChange: (e) => setForm({ ...form, description: e.target.value })
402
+ }
403
+ )
404
+ ] }),
405
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
406
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "logo_image", children: "Logo Image URL" }),
407
+ /* @__PURE__ */ jsxRuntime.jsx(
408
+ ui.Input,
409
+ {
410
+ id: "logo_image",
411
+ type: "url",
412
+ value: form.logo_image,
413
+ onChange: (e) => setForm({ ...form, logo_image: e.target.value })
414
+ }
415
+ )
416
+ ] }),
417
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
418
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "cover_image", children: "Cover Image URL" }),
419
+ /* @__PURE__ */ jsxRuntime.jsx(
420
+ ui.Input,
421
+ {
422
+ id: "cover_image",
423
+ type: "url",
424
+ value: form.cover_image,
425
+ onChange: (e) => setForm({ ...form, cover_image: e.target.value })
426
+ }
427
+ )
428
+ ] }),
429
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
430
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", isLoading: updateMutation.isPending, children: "Save Changes" }),
431
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate(`/brands/${id}`), children: "Cancel" })
432
+ ] })
433
+ ] })
434
+ ] });
435
+ };
436
+ const widgetModule = { widgets: [] };
437
+ const routeModule = {
438
+ routes: [
439
+ {
440
+ Component: BrandsPage,
441
+ path: "/brands"
442
+ },
443
+ {
444
+ Component: BrandDetailPage,
445
+ path: "/brands/:id"
446
+ },
447
+ {
448
+ Component: CreateBrandPage,
449
+ path: "/brands/create"
450
+ },
451
+ {
452
+ Component: EditBrandPage,
453
+ path: "/brands/:id/edit"
454
+ }
455
+ ]
456
+ };
457
+ const menuItemModule = {
458
+ menuItems: [
459
+ {
460
+ label: config.label,
461
+ icon: config.icon,
462
+ path: "/brands",
463
+ nested: void 0,
464
+ rank: void 0,
465
+ translationNs: void 0
466
+ }
467
+ ]
468
+ };
469
+ const formModule = { customFields: {} };
470
+ const displayModule = {
471
+ displays: {}
472
+ };
473
+ const i18nModule = { resources: {} };
474
+ const plugin = {
475
+ widgetModule,
476
+ routeModule,
477
+ menuItemModule,
478
+ formModule,
479
+ displayModule,
480
+ i18nModule
481
+ };
482
+ module.exports = plugin;