@lodashventure/medusa-brand 1.1.12 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/api/admin/brands/[id]/image/route.js +119 -0
- package/.medusa/server/api/admin/brands/[id]/logo/route.js +119 -0
- package/.medusa/server/api/admin/brands/[id]/products/route.js +52 -0
- package/.medusa/server/api/admin/brands/[id]/route.js +112 -0
- package/.medusa/server/api/admin/brands/route.js +76 -0
- package/.medusa/server/api/admin/products/[id]/brand/route.js +117 -0
- package/{dist → .medusa/server}/api/middlewares/attach-brand-to-products.js +1 -0
- package/{dist → .medusa/server}/api/middlewares.js +1 -0
- package/.medusa/server/api/store/brands/route.js +53 -0
- package/{dist → .medusa/server}/index.js +1 -0
- package/{dist → .medusa/server}/modules/brand/index.js +1 -0
- package/{dist → .medusa/server}/modules/brand/migrations/Migration20251021070648.js +1 -0
- package/.medusa/server/modules/brand/models/brand.js +43 -0
- package/.medusa/server/modules/brand/service.js +11 -0
- package/.medusa/server/services/gcs-direct-upload.js +55 -0
- package/.medusa/server/src/admin/index.js +972 -0
- package/.medusa/server/src/admin/index.mjs +971 -0
- package/.medusa/server/workflows/upload-brand-image.js +57 -0
- package/package.json +14 -13
- package/dist/admin/components/brand-form.d.ts +0 -19
- package/dist/admin/components/brand-form.js +0 -182
- package/dist/admin/components/brand-image-uploader.d.ts +0 -14
- package/dist/admin/components/brand-image-uploader.js +0 -217
- package/dist/admin/lib/sdk.d.ts +0 -1
- package/dist/admin/lib/sdk.js +0 -14
- package/dist/admin/routes/brands/page.d.ts +0 -4
- package/dist/admin/routes/brands/page.js +0 -253
- package/dist/admin/widgets/product-brand-widget.d.ts +0 -8
- package/dist/admin/widgets/product-brand-widget.js +0 -207
- package/dist/api/admin/brands/[id]/image/route.js +0 -118
- package/dist/api/admin/brands/[id]/logo/route.js +0 -118
- package/dist/api/admin/brands/[id]/products/route.js +0 -51
- package/dist/api/admin/brands/[id]/route.js +0 -111
- package/dist/api/admin/brands/route.js +0 -75
- package/dist/api/admin/products/[id]/brand/route.js +0 -116
- package/dist/api/store/brands/route.js +0 -50
- package/dist/modules/brand/models/brand.js +0 -42
- package/dist/modules/brand/service.js +0 -10
- package/dist/services/gcs-direct-upload.js +0 -54
- package/dist/workflows/upload-brand-image.js +0 -56
- /package/{dist → .medusa/server}/api/admin/brands/[id]/image/route.d.ts +0 -0
- /package/{dist → .medusa/server}/api/admin/brands/[id]/logo/route.d.ts +0 -0
- /package/{dist → .medusa/server}/api/admin/brands/[id]/products/route.d.ts +0 -0
- /package/{dist → .medusa/server}/api/admin/brands/[id]/route.d.ts +0 -0
- /package/{dist → .medusa/server}/api/admin/brands/route.d.ts +0 -0
- /package/{dist → .medusa/server}/api/admin/products/[id]/brand/route.d.ts +0 -0
- /package/{dist → .medusa/server}/api/middlewares/attach-brand-to-products.d.ts +0 -0
- /package/{dist → .medusa/server}/api/middlewares.d.ts +0 -0
- /package/{dist → .medusa/server}/api/store/brands/route.d.ts +0 -0
- /package/{dist → .medusa/server}/index.d.ts +0 -0
- /package/{dist → .medusa/server}/modules/brand/index.d.ts +0 -0
- /package/{dist → .medusa/server}/modules/brand/migrations/Migration20251021070648.d.ts +0 -0
- /package/{dist → .medusa/server}/modules/brand/models/brand.d.ts +0 -0
- /package/{dist → .medusa/server}/modules/brand/service.d.ts +0 -0
- /package/{dist → .medusa/server}/services/gcs-direct-upload.d.ts +0 -0
- /package/{dist → .medusa/server}/workflows/upload-brand-image.d.ts +0 -0
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.config = void 0;
|
|
37
|
-
const admin_sdk_1 = require("@medusajs/admin-sdk");
|
|
38
|
-
const sdk_1 = require("../lib/sdk");
|
|
39
|
-
if (["$(basename $file)" = "page.tsx"] || "../lib/sdk")
|
|
40
|
-
;
|
|
41
|
-
const ui_1 = require("@medusajs/ui");
|
|
42
|
-
const icons_1 = require("@medusajs/icons");
|
|
43
|
-
const react_1 = __importStar(require("react"));
|
|
44
|
-
const brand_form_1 = require("../../components/brand-form");
|
|
45
|
-
const brand_image_uploader_1 = require("../../components/brand-image-uploader");
|
|
46
|
-
const BrandsPage = () => {
|
|
47
|
-
const [brands, setBrands] = (0, react_1.useState)([]);
|
|
48
|
-
const [loading, setLoading] = (0, react_1.useState)(true);
|
|
49
|
-
const [error, setError] = (0, react_1.useState)(null);
|
|
50
|
-
const [selectedBrand, setSelectedBrand] = (0, react_1.useState)(null);
|
|
51
|
-
const [showForm, setShowForm] = (0, react_1.useState)(false);
|
|
52
|
-
const [showImageUploader, setShowImageUploader] = (0, react_1.useState)(false);
|
|
53
|
-
const [imageType, setImageType] = (0, react_1.useState)("image");
|
|
54
|
-
const [searchQuery, setSearchQuery] = (0, react_1.useState)("");
|
|
55
|
-
const [isCreating, setIsCreating] = (0, react_1.useState)(false);
|
|
56
|
-
(0, react_1.useEffect)(() => {
|
|
57
|
-
fetchBrands();
|
|
58
|
-
}, []);
|
|
59
|
-
const fetchBrands = async () => {
|
|
60
|
-
setLoading(true);
|
|
61
|
-
setError(null);
|
|
62
|
-
try {
|
|
63
|
-
const queryParams = new URLSearchParams({ limit: "100" });
|
|
64
|
-
if (searchQuery) {
|
|
65
|
-
queryParams.append("q", searchQuery);
|
|
66
|
-
}
|
|
67
|
-
const response = await sdk_1.sdk.client.fetch(`/admin/brands?${queryParams}`, {});
|
|
68
|
-
if (response.ok) {
|
|
69
|
-
const data = await response.json();
|
|
70
|
-
setBrands(data.brands || []);
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
setError("Failed to fetch brands");
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
catch (err) {
|
|
77
|
-
setError("Error fetching brands");
|
|
78
|
-
console.error("Error fetching brands:", err);
|
|
79
|
-
}
|
|
80
|
-
finally {
|
|
81
|
-
setLoading(false);
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
const handleCreateBrand = () => {
|
|
85
|
-
setIsCreating(true);
|
|
86
|
-
setSelectedBrand(null);
|
|
87
|
-
setShowForm(true);
|
|
88
|
-
};
|
|
89
|
-
const handleEditBrand = (brand) => {
|
|
90
|
-
setIsCreating(false);
|
|
91
|
-
setSelectedBrand(brand);
|
|
92
|
-
setShowForm(true);
|
|
93
|
-
};
|
|
94
|
-
const handleDeleteBrand = async (brandId) => {
|
|
95
|
-
if (!confirm("Are you sure you want to delete this brand? This action cannot be undone.")) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
try {
|
|
99
|
-
const response = await sdk_1.sdk.client.fetch(`/admin/brands/${brandId}`, {
|
|
100
|
-
method: "DELETE",
|
|
101
|
-
});
|
|
102
|
-
if (response.ok) {
|
|
103
|
-
fetchBrands();
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
alert("Failed to delete brand");
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
alert("Error deleting brand");
|
|
111
|
-
console.error("Error deleting brand:", err);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
const handleToggleActive = async (brand) => {
|
|
115
|
-
try {
|
|
116
|
-
const response = await sdk_1.sdk.client.fetch(`/admin/brands/${brand.id}`, {
|
|
117
|
-
method: "PUT",
|
|
118
|
-
headers: {
|
|
119
|
-
"Content-Type": "application/json",
|
|
120
|
-
},
|
|
121
|
-
body: JSON.stringify({
|
|
122
|
-
is_active: !brand.is_active,
|
|
123
|
-
}),
|
|
124
|
-
});
|
|
125
|
-
if (response.ok) {
|
|
126
|
-
fetchBrands();
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
alert("Failed to update brand status");
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
catch (err) {
|
|
133
|
-
alert("Error updating brand status");
|
|
134
|
-
console.error("Error updating brand status:", err);
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
const handleImageUpload = (brand, type) => {
|
|
138
|
-
setSelectedBrand(brand);
|
|
139
|
-
setImageType(type);
|
|
140
|
-
setShowImageUploader(true);
|
|
141
|
-
};
|
|
142
|
-
const handleFormClose = () => {
|
|
143
|
-
setShowForm(false);
|
|
144
|
-
setSelectedBrand(null);
|
|
145
|
-
setIsCreating(false);
|
|
146
|
-
fetchBrands();
|
|
147
|
-
};
|
|
148
|
-
const handleImageUploaderClose = () => {
|
|
149
|
-
setShowImageUploader(false);
|
|
150
|
-
setSelectedBrand(null);
|
|
151
|
-
fetchBrands();
|
|
152
|
-
};
|
|
153
|
-
const filteredBrands = brands.filter(brand => {
|
|
154
|
-
if (!searchQuery)
|
|
155
|
-
return true;
|
|
156
|
-
const query = searchQuery.toLowerCase();
|
|
157
|
-
return (brand.name.toLowerCase().includes(query) ||
|
|
158
|
-
brand.slug.toLowerCase().includes(query) ||
|
|
159
|
-
brand.description?.toLowerCase().includes(query));
|
|
160
|
-
});
|
|
161
|
-
if (loading) {
|
|
162
|
-
return (react_1.default.createElement(ui_1.Container, null,
|
|
163
|
-
react_1.default.createElement("div", { className: "flex h-64 items-center justify-center" },
|
|
164
|
-
react_1.default.createElement(ui_1.Text, null, "Loading brands..."))));
|
|
165
|
-
}
|
|
166
|
-
if (error) {
|
|
167
|
-
return (react_1.default.createElement(ui_1.Container, null,
|
|
168
|
-
react_1.default.createElement(ui_1.Alert, { variant: "error", dismissible: true }, error)));
|
|
169
|
-
}
|
|
170
|
-
return (react_1.default.createElement(ui_1.Container, null,
|
|
171
|
-
react_1.default.createElement("div", { className: "mb-8" },
|
|
172
|
-
react_1.default.createElement("div", { className: "flex items-center justify-between mb-4" },
|
|
173
|
-
react_1.default.createElement("div", null,
|
|
174
|
-
react_1.default.createElement(ui_1.Heading, { level: "h1", className: "mb-2" }, "Brands"),
|
|
175
|
-
react_1.default.createElement(ui_1.Text, { className: "text-ui-fg-subtle" }, "Manage your product brands and their images")),
|
|
176
|
-
react_1.default.createElement(ui_1.Button, { onClick: handleCreateBrand },
|
|
177
|
-
react_1.default.createElement(icons_1.Plus, { className: "mr-2" }),
|
|
178
|
-
"Create Brand")),
|
|
179
|
-
react_1.default.createElement("div", { className: "flex items-center gap-4" },
|
|
180
|
-
react_1.default.createElement("div", { className: "relative flex-1 max-w-md" },
|
|
181
|
-
react_1.default.createElement(ui_1.Input, { placeholder: "Search brands...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), className: "pl-10" }),
|
|
182
|
-
react_1.default.createElement(icons_1.MagnifyingGlass, { className: "absolute left-3 top-1/2 -translate-y-1/2 text-ui-fg-muted" })))),
|
|
183
|
-
filteredBrands.length === 0 ? (react_1.default.createElement("div", { className: "flex h-64 flex-col items-center justify-center rounded-lg border-2 border-dashed border-ui-border-base" },
|
|
184
|
-
react_1.default.createElement(icons_1.BuildingStorefront, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }),
|
|
185
|
-
react_1.default.createElement(ui_1.Text, { className: "mb-2 text-lg font-medium" }, searchQuery ? "No brands found" : "No brands yet"),
|
|
186
|
-
react_1.default.createElement(ui_1.Text, { className: "mb-4 text-ui-fg-subtle" }, searchQuery
|
|
187
|
-
? "Try adjusting your search query"
|
|
188
|
-
: "Create your first brand to get started"),
|
|
189
|
-
!searchQuery && (react_1.default.createElement(ui_1.Button, { onClick: handleCreateBrand },
|
|
190
|
-
react_1.default.createElement(icons_1.Plus, { className: "mr-2" }),
|
|
191
|
-
"Create Brand")))) : (react_1.default.createElement("div", { className: "overflow-hidden rounded-lg border" },
|
|
192
|
-
react_1.default.createElement(ui_1.Table, null,
|
|
193
|
-
react_1.default.createElement(ui_1.Table.Header, null,
|
|
194
|
-
react_1.default.createElement(ui_1.Table.Row, null,
|
|
195
|
-
react_1.default.createElement(ui_1.Table.HeaderCell, null, "Logo"),
|
|
196
|
-
react_1.default.createElement(ui_1.Table.HeaderCell, null, "Name"),
|
|
197
|
-
react_1.default.createElement(ui_1.Table.HeaderCell, null, "Slug"),
|
|
198
|
-
react_1.default.createElement(ui_1.Table.HeaderCell, null, "Status"),
|
|
199
|
-
react_1.default.createElement(ui_1.Table.HeaderCell, null, "Images"),
|
|
200
|
-
react_1.default.createElement(ui_1.Table.HeaderCell, { className: "text-right" }, "Actions"))),
|
|
201
|
-
react_1.default.createElement(ui_1.Table.Body, null, filteredBrands.map((brand) => (react_1.default.createElement(ui_1.Table.Row, { key: brand.id },
|
|
202
|
-
react_1.default.createElement(ui_1.Table.Cell, null, brand.logo ? (react_1.default.createElement("img", { src: brand.logo, alt: `${brand.name} logo`, className: "h-10 w-10 object-contain rounded" })) : (react_1.default.createElement("div", { className: "flex h-10 w-10 items-center justify-center rounded bg-ui-bg-subtle" },
|
|
203
|
-
react_1.default.createElement(icons_1.BuildingStorefront, { className: "h-5 w-5 text-ui-fg-subtle" })))),
|
|
204
|
-
react_1.default.createElement(ui_1.Table.Cell, null,
|
|
205
|
-
react_1.default.createElement("div", null,
|
|
206
|
-
react_1.default.createElement(ui_1.Text, { className: "font-medium" }, brand.name),
|
|
207
|
-
brand.website && (react_1.default.createElement(ui_1.Text, { className: "text-xs text-ui-fg-subtle" }, brand.website)))),
|
|
208
|
-
react_1.default.createElement(ui_1.Table.Cell, null,
|
|
209
|
-
react_1.default.createElement(ui_1.Badge, { color: "blue" }, brand.slug)),
|
|
210
|
-
react_1.default.createElement(ui_1.Table.Cell, null,
|
|
211
|
-
react_1.default.createElement(ui_1.Badge, { color: brand.is_active ? "green" : "grey" }, brand.is_active ? "Active" : "Inactive")),
|
|
212
|
-
react_1.default.createElement(ui_1.Table.Cell, null,
|
|
213
|
-
react_1.default.createElement("div", { className: "flex gap-2" },
|
|
214
|
-
brand.image && (react_1.default.createElement(ui_1.Badge, { color: "green", size: "xsmall" },
|
|
215
|
-
react_1.default.createElement(icons_1.PhotoSolid, { className: "mr-1" }),
|
|
216
|
-
"Image")),
|
|
217
|
-
brand.logo && (react_1.default.createElement(ui_1.Badge, { color: "green", size: "xsmall" },
|
|
218
|
-
react_1.default.createElement(icons_1.BuildingStorefront, { className: "mr-1" }),
|
|
219
|
-
"Logo")),
|
|
220
|
-
!brand.image && !brand.logo && (react_1.default.createElement(ui_1.Text, { className: "text-sm text-ui-fg-muted" }, "No images")))),
|
|
221
|
-
react_1.default.createElement(ui_1.Table.Cell, null,
|
|
222
|
-
react_1.default.createElement("div", { className: "flex items-center justify-end gap-2" },
|
|
223
|
-
react_1.default.createElement(ui_1.DropdownMenu, null,
|
|
224
|
-
react_1.default.createElement(ui_1.DropdownMenu.Trigger, { asChild: true },
|
|
225
|
-
react_1.default.createElement(ui_1.IconButton, { variant: "transparent", size: "small" },
|
|
226
|
-
react_1.default.createElement(icons_1.EllipsisHorizontal, null))),
|
|
227
|
-
react_1.default.createElement(ui_1.DropdownMenu.Content, null,
|
|
228
|
-
react_1.default.createElement(ui_1.DropdownMenu.Item, { onClick: () => handleEditBrand(brand) },
|
|
229
|
-
react_1.default.createElement(icons_1.PencilSquare, { className: "mr-2" }),
|
|
230
|
-
"Edit Details"),
|
|
231
|
-
react_1.default.createElement(ui_1.DropdownMenu.Separator, null),
|
|
232
|
-
react_1.default.createElement(ui_1.DropdownMenu.Item, { onClick: () => handleImageUpload(brand, "image") },
|
|
233
|
-
react_1.default.createElement(icons_1.CloudArrowUp, { className: "mr-2" }),
|
|
234
|
-
brand.image ? "Replace Image" : "Upload Image"),
|
|
235
|
-
react_1.default.createElement(ui_1.DropdownMenu.Item, { onClick: () => handleImageUpload(brand, "logo") },
|
|
236
|
-
react_1.default.createElement(icons_1.CloudArrowUp, { className: "mr-2" }),
|
|
237
|
-
brand.logo ? "Replace Logo" : "Upload Logo"),
|
|
238
|
-
react_1.default.createElement(ui_1.DropdownMenu.Separator, null),
|
|
239
|
-
react_1.default.createElement(ui_1.DropdownMenu.Item, { onClick: () => handleToggleActive(brand) },
|
|
240
|
-
react_1.default.createElement(ui_1.Switch, { checked: brand.is_active, className: "mr-2 pointer-events-none" }),
|
|
241
|
-
brand.is_active ? "Deactivate" : "Activate"),
|
|
242
|
-
react_1.default.createElement(ui_1.DropdownMenu.Separator, null),
|
|
243
|
-
react_1.default.createElement(ui_1.DropdownMenu.Item, { onClick: () => handleDeleteBrand(brand.id), className: "text-ui-fg-error" },
|
|
244
|
-
react_1.default.createElement(icons_1.Trash, { className: "mr-2" }),
|
|
245
|
-
"Delete Brand")))))))))))),
|
|
246
|
-
showForm && (react_1.default.createElement(brand_form_1.BrandForm, { brand: selectedBrand, isCreating: isCreating, onClose: handleFormClose })),
|
|
247
|
-
showImageUploader && selectedBrand && (react_1.default.createElement(brand_image_uploader_1.BrandImageUploader, { brand: selectedBrand, imageType: imageType, onClose: handleImageUploaderClose }))));
|
|
248
|
-
};
|
|
249
|
-
exports.config = (0, admin_sdk_1.defineRouteConfig)({
|
|
250
|
-
label: "Brands",
|
|
251
|
-
icon: icons_1.BuildingStorefront,
|
|
252
|
-
});
|
|
253
|
-
exports.default = BrandsPage;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { AdminProduct } from "@medusajs/types";
|
|
3
|
-
interface ProductBrandWidgetProps {
|
|
4
|
-
data: AdminProduct;
|
|
5
|
-
}
|
|
6
|
-
declare const ProductBrandWidget: ({ data: product }: ProductBrandWidgetProps) => React.JSX.Element;
|
|
7
|
-
export declare const config: import("@medusajs/admin-sdk").WidgetConfig;
|
|
8
|
-
export default ProductBrandWidget;
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.config = void 0;
|
|
37
|
-
const admin_sdk_1 = require("@medusajs/admin-sdk");
|
|
38
|
-
const sdk_1 = require("../lib/sdk");
|
|
39
|
-
if (["$(basename $file)" = "page.tsx"] || "../lib/sdk")
|
|
40
|
-
;
|
|
41
|
-
const ui_1 = require("@medusajs/ui");
|
|
42
|
-
const icons_1 = require("@medusajs/icons");
|
|
43
|
-
const react_1 = __importStar(require("react"));
|
|
44
|
-
const ProductBrandWidget = ({ data: product }) => {
|
|
45
|
-
const [brands, setBrands] = (0, react_1.useState)([]);
|
|
46
|
-
const [currentBrand, setCurrentBrand] = (0, react_1.useState)(null);
|
|
47
|
-
const [selectedBrandId, setSelectedBrandId] = (0, react_1.useState)("none");
|
|
48
|
-
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
49
|
-
const [error, setError] = (0, react_1.useState)(null);
|
|
50
|
-
const [success, setSuccess] = (0, react_1.useState)(null);
|
|
51
|
-
(0, react_1.useEffect)(() => {
|
|
52
|
-
fetchBrands();
|
|
53
|
-
fetchProductBrand();
|
|
54
|
-
}, [product.id]);
|
|
55
|
-
const fetchBrands = async () => {
|
|
56
|
-
try {
|
|
57
|
-
const response = await sdk_1.sdk.client.fetch("/admin/brands?is_active=true&limit=100", {});
|
|
58
|
-
if (response.ok) {
|
|
59
|
-
const data = await response.json();
|
|
60
|
-
setBrands(data.brands || []);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
catch (err) {
|
|
64
|
-
console.error("Error fetching brands:", err);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
const fetchProductBrand = async () => {
|
|
68
|
-
try {
|
|
69
|
-
const response = await sdk_1.sdk.client.fetch(`/admin/products/${product.id}/brand`, {});
|
|
70
|
-
if (response.ok) {
|
|
71
|
-
const data = await response.json();
|
|
72
|
-
if (data.brand) {
|
|
73
|
-
setCurrentBrand(data.brand);
|
|
74
|
-
setSelectedBrandId(data.brand.id);
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
setCurrentBrand(null);
|
|
78
|
-
setSelectedBrandId("none");
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch (err) {
|
|
83
|
-
console.error("Error fetching product brand:", err);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
const handleBrandChange = (brandId) => {
|
|
87
|
-
setSelectedBrandId(brandId);
|
|
88
|
-
setError(null);
|
|
89
|
-
setSuccess(null);
|
|
90
|
-
};
|
|
91
|
-
const handleSaveBrand = async () => {
|
|
92
|
-
if (!selectedBrandId || selectedBrandId === "none") {
|
|
93
|
-
// If "No brand" is selected, remove the brand
|
|
94
|
-
if (currentBrand) {
|
|
95
|
-
await handleRemoveBrand();
|
|
96
|
-
}
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
setLoading(true);
|
|
100
|
-
setError(null);
|
|
101
|
-
setSuccess(null);
|
|
102
|
-
try {
|
|
103
|
-
const response = await sdk_1.sdk.client.fetch(`/admin/products/${product.id}/brand`, {
|
|
104
|
-
method: "POST",
|
|
105
|
-
headers: {
|
|
106
|
-
"Content-Type": "application/json",
|
|
107
|
-
},
|
|
108
|
-
body: JSON.stringify({ brand_id: selectedBrandId }),
|
|
109
|
-
});
|
|
110
|
-
if (response.ok) {
|
|
111
|
-
setSuccess("Brand updated successfully");
|
|
112
|
-
fetchProductBrand();
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
const data = await response.json();
|
|
116
|
-
setError(data.error || "Failed to update brand");
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
catch (err) {
|
|
120
|
-
setError("Error updating brand");
|
|
121
|
-
console.error("Error updating brand:", err);
|
|
122
|
-
}
|
|
123
|
-
finally {
|
|
124
|
-
setLoading(false);
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
const handleRemoveBrand = async () => {
|
|
128
|
-
if (!confirm("Are you sure you want to remove the brand from this product?")) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
setLoading(true);
|
|
132
|
-
setError(null);
|
|
133
|
-
setSuccess(null);
|
|
134
|
-
try {
|
|
135
|
-
const response = await sdk_1.sdk.client.fetch(`/admin/products/${product.id}/brand`, {
|
|
136
|
-
method: "DELETE",
|
|
137
|
-
});
|
|
138
|
-
if (response.ok) {
|
|
139
|
-
setSuccess("Brand removed successfully");
|
|
140
|
-
setCurrentBrand(null);
|
|
141
|
-
setSelectedBrandId("");
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
const data = await response.json();
|
|
145
|
-
setError(data.error || "Failed to remove brand");
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
catch (err) {
|
|
149
|
-
setError("Error removing brand");
|
|
150
|
-
console.error("Error removing brand:", err);
|
|
151
|
-
}
|
|
152
|
-
finally {
|
|
153
|
-
setLoading(false);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
const hasChanges = selectedBrandId !== (currentBrand?.id || "none");
|
|
157
|
-
return (react_1.default.createElement(ui_1.Container, { className: "divide-y px-0 pb-0 pt-0" },
|
|
158
|
-
react_1.default.createElement("div", { className: "px-6 py-6" },
|
|
159
|
-
react_1.default.createElement("div", { className: "flex items-center gap-3 mb-4" },
|
|
160
|
-
react_1.default.createElement(icons_1.BuildingStorefront, { className: "text-ui-fg-subtle" }),
|
|
161
|
-
react_1.default.createElement(ui_1.Heading, { level: "h2" }, "Brand")),
|
|
162
|
-
error && (react_1.default.createElement(ui_1.Alert, { variant: "error", dismissible: true, className: "mb-4" }, error)),
|
|
163
|
-
success && (react_1.default.createElement(ui_1.Alert, { variant: "success", dismissible: true, className: "mb-4" }, success)),
|
|
164
|
-
currentBrand && !hasChanges && (react_1.default.createElement("div", { className: "mb-4 p-4 rounded-lg border bg-ui-bg-subtle" },
|
|
165
|
-
react_1.default.createElement("div", { className: "flex items-center justify-between" },
|
|
166
|
-
react_1.default.createElement("div", { className: "flex items-center gap-3" },
|
|
167
|
-
currentBrand.logo ? (react_1.default.createElement("img", { src: currentBrand.logo, alt: currentBrand.name, className: "h-10 w-10 object-contain rounded" })) : (react_1.default.createElement("div", { className: "h-10 w-10 rounded bg-ui-bg-base flex items-center justify-center" },
|
|
168
|
-
react_1.default.createElement(icons_1.BuildingStorefront, { className: "h-5 w-5 text-ui-fg-subtle" }))),
|
|
169
|
-
react_1.default.createElement("div", null,
|
|
170
|
-
react_1.default.createElement(ui_1.Text, { className: "font-medium" }, currentBrand.name),
|
|
171
|
-
react_1.default.createElement(ui_1.Text, { className: "text-sm text-ui-fg-subtle" },
|
|
172
|
-
"/",
|
|
173
|
-
currentBrand.slug))),
|
|
174
|
-
react_1.default.createElement(ui_1.Button, { variant: "danger", size: "small", onClick: handleRemoveBrand, disabled: loading },
|
|
175
|
-
react_1.default.createElement(icons_1.Trash, { className: "mr-1" }),
|
|
176
|
-
"Remove")))),
|
|
177
|
-
react_1.default.createElement("div", { className: "space-y-4" },
|
|
178
|
-
react_1.default.createElement("div", null,
|
|
179
|
-
react_1.default.createElement(ui_1.Label, { htmlFor: "brand", className: "mb-2" }, "Select Brand"),
|
|
180
|
-
react_1.default.createElement(ui_1.Select, { value: selectedBrandId, onValueChange: handleBrandChange, disabled: loading },
|
|
181
|
-
react_1.default.createElement(ui_1.Select.Trigger, { id: "brand" },
|
|
182
|
-
react_1.default.createElement(ui_1.Select.Value, { placeholder: "Choose a brand..." })),
|
|
183
|
-
react_1.default.createElement(ui_1.Select.Content, null,
|
|
184
|
-
react_1.default.createElement(ui_1.Select.Item, { value: "none" },
|
|
185
|
-
react_1.default.createElement("span", { className: "text-ui-fg-muted" }, "No brand")),
|
|
186
|
-
brands.map((brand) => (react_1.default.createElement(ui_1.Select.Item, { key: brand.id, value: brand.id },
|
|
187
|
-
react_1.default.createElement("div", { className: "flex items-center gap-2" },
|
|
188
|
-
brand.logo && (react_1.default.createElement("img", { src: brand.logo, alt: brand.name, className: "h-5 w-5 object-contain rounded" })),
|
|
189
|
-
react_1.default.createElement("span", null, brand.name),
|
|
190
|
-
react_1.default.createElement(ui_1.Badge, { size: "xsmall", color: "blue", className: "ml-auto" }, brand.slug))))))),
|
|
191
|
-
react_1.default.createElement(ui_1.Text, { className: "mt-1 text-xs text-ui-fg-subtle" }, "Assign a brand to help customers find products by their favorite brands")),
|
|
192
|
-
hasChanges && (react_1.default.createElement("div", { className: "flex gap-2" },
|
|
193
|
-
react_1.default.createElement(ui_1.Button, { variant: "primary", size: "small", onClick: handleSaveBrand, disabled: loading }, loading ? "Saving..." : "Save Brand"),
|
|
194
|
-
react_1.default.createElement(ui_1.Button, { variant: "secondary", size: "small", onClick: () => {
|
|
195
|
-
setSelectedBrandId(currentBrand?.id || "none");
|
|
196
|
-
setError(null);
|
|
197
|
-
setSuccess(null);
|
|
198
|
-
}, disabled: loading }, "Cancel")))),
|
|
199
|
-
react_1.default.createElement("div", { className: "mt-6 pt-6 border-t" },
|
|
200
|
-
react_1.default.createElement("div", { className: "flex items-center gap-2 text-sm" },
|
|
201
|
-
react_1.default.createElement(icons_1.Link, { className: "text-ui-fg-subtle" }),
|
|
202
|
-
react_1.default.createElement("a", { href: "/app/brands", className: "text-ui-fg-interactive hover:text-ui-fg-interactive-hover transition-colors" }, "Manage all brands"))))));
|
|
203
|
-
};
|
|
204
|
-
exports.config = (0, admin_sdk_1.defineWidgetConfig)({
|
|
205
|
-
zone: "product.details.side.after",
|
|
206
|
-
});
|
|
207
|
-
exports.default = ProductBrandWidget;
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DELETE = exports.POST = exports.GET = void 0;
|
|
4
|
-
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
-
const upload_brand_image_1 = require("../../../../../workflows/upload-brand-image");
|
|
6
|
-
const gcs_direct_upload_1 = require("../../../../../services/gcs-direct-upload");
|
|
7
|
-
// GET - Get brand image
|
|
8
|
-
const GET = async (req, res) => {
|
|
9
|
-
const { id } = req.params;
|
|
10
|
-
const brandService = req.scope.resolve("brandCustom");
|
|
11
|
-
try {
|
|
12
|
-
const brand = await brandService.retrieveBrand(id);
|
|
13
|
-
if (!brand) {
|
|
14
|
-
return res.status(404).json({ error: "Brand not found" });
|
|
15
|
-
}
|
|
16
|
-
return res.json({
|
|
17
|
-
image: brand.image || null,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
console.error("Error fetching brand image:", error);
|
|
22
|
-
return res.status(500).json({ error: "Failed to fetch brand image" });
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
exports.GET = GET;
|
|
26
|
-
// POST - Upload brand image
|
|
27
|
-
const POST = async (req, res) => {
|
|
28
|
-
const { id } = req.params;
|
|
29
|
-
const brandService = req.scope.resolve("brandCustom");
|
|
30
|
-
const logger = req.scope.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
|
|
31
|
-
try {
|
|
32
|
-
const brand = await brandService.retrieveBrand(id);
|
|
33
|
-
if (!brand) {
|
|
34
|
-
return res.status(404).json({ error: "Brand not found" });
|
|
35
|
-
}
|
|
36
|
-
const file = req.file;
|
|
37
|
-
if (!file) {
|
|
38
|
-
return res.status(400).json({ error: "No file uploaded" });
|
|
39
|
-
}
|
|
40
|
-
// Validate file type
|
|
41
|
-
const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
|
|
42
|
-
if (!allowedMimeTypes.includes(file.mimetype)) {
|
|
43
|
-
return res.status(400).json({
|
|
44
|
-
error: "Invalid file type. Only JPEG, PNG, GIF, and WebP images are allowed."
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
// Validate file size (max 5MB)
|
|
48
|
-
if (file.size > 5 * 1024 * 1024) {
|
|
49
|
-
return res.status(400).json({
|
|
50
|
-
error: "File size too large. Maximum allowed size is 5MB."
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
const result = await (0, upload_brand_image_1.uploadBrandImageWorkflow)(req.scope).run({
|
|
54
|
-
input: {
|
|
55
|
-
brandId: id,
|
|
56
|
-
imageType: "image",
|
|
57
|
-
fileData: {
|
|
58
|
-
filename: file.originalname,
|
|
59
|
-
mimeType: file.mimetype,
|
|
60
|
-
content: file.buffer.toString("base64"),
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
return res.json({
|
|
65
|
-
brand: {
|
|
66
|
-
id,
|
|
67
|
-
image: result.result.imageUrl,
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
logger.error("Error uploading brand image:", error);
|
|
73
|
-
return res.status(500).json({ error: "Failed to upload brand image" });
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
exports.POST = POST;
|
|
77
|
-
// DELETE - Delete brand image
|
|
78
|
-
const DELETE = async (req, res) => {
|
|
79
|
-
const { id } = req.params;
|
|
80
|
-
const brandService = req.scope.resolve("brandCustom");
|
|
81
|
-
const logger = req.scope.resolve(utils_1.ContainerRegistrationKeys.LOGGER);
|
|
82
|
-
const gcsUploadService = new gcs_direct_upload_1.GcsDirectUploadService();
|
|
83
|
-
try {
|
|
84
|
-
const brand = await brandService.retrieveBrand(id);
|
|
85
|
-
if (!brand) {
|
|
86
|
-
return res.status(404).json({ error: "Brand not found" });
|
|
87
|
-
}
|
|
88
|
-
if (!brand.image) {
|
|
89
|
-
return res.status(404).json({ error: "No image found for this brand" });
|
|
90
|
-
}
|
|
91
|
-
// Delete the file from GCS
|
|
92
|
-
if (brand.image.includes('storage.googleapis.com')) {
|
|
93
|
-
try {
|
|
94
|
-
const parts = brand.image.split('/');
|
|
95
|
-
const bucketIndex = parts.indexOf('sangaroon');
|
|
96
|
-
if (bucketIndex !== -1 && bucketIndex < parts.length - 1) {
|
|
97
|
-
const filename = parts.slice(bucketIndex + 1).join('/');
|
|
98
|
-
await gcsUploadService.deleteFile(filename);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch (deleteError) {
|
|
102
|
-
logger.error("Error deleting file from GCS:", deleteError);
|
|
103
|
-
// Continue even if file deletion fails
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
await brandService.updateBrands([
|
|
107
|
-
{ id: brand.id, image: null }
|
|
108
|
-
]);
|
|
109
|
-
return res.json({
|
|
110
|
-
message: "Brand image deleted successfully",
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
logger.error("Error deleting brand image:", error);
|
|
115
|
-
return res.status(500).json({ error: "Failed to delete brand image" });
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
exports.DELETE = DELETE;
|