@lodashventure/medusa-campaign 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/admin/index.js +939 -504
- package/.medusa/server/src/admin/index.mjs +941 -506
- package/.medusa/server/src/api/admin/buy-x-get-y/[id]/route.js +2 -6
- package/.medusa/server/src/api/admin/coupons/[id]/route.js +76 -0
- package/.medusa/server/src/api/admin/coupons/route.js +88 -0
- package/.medusa/server/src/api/middlewares.js +32 -1
- package/.medusa/server/src/api/store/campaigns/route.js +78 -7
- package/.medusa/server/src/api/store/coupons/public/route.js +110 -0
- package/.medusa/server/src/api/store/customers/me/coupons/route.js +148 -0
- package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251024000000.js +2 -2
- package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251025000000.js +2 -2
- package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251101000000.js +16 -0
- package/.medusa/server/src/modules/custom-campaigns/types/campaign-type.enum.js +2 -1
- package/.medusa/server/src/workflows/custom-campaign/createCouponCampaignWorkflow.js +105 -0
- package/.medusa/server/src/workflows/custom-campaign/updateCouponCampaignWorkflow.js +59 -0
- package/.medusa/server/src/workflows/index.js +6 -2
- package/package.json +15 -30
- package/src/admin/components/BuyXGetYForm.tsx +24 -13
- package/src/admin/components/CouponForm.tsx +352 -0
- package/src/admin/components/CouponPage.tsx +104 -0
- package/src/admin/components/ProductSelector.tsx +22 -11
- package/src/admin/hooks/useCouponById.ts +36 -0
- package/src/admin/hooks/useCoupons.ts +46 -0
- package/src/admin/hooks/useFlashSaleById.ts +36 -10
- package/src/admin/hooks/useFlashSales.ts +36 -10
- package/src/admin/routes/coupons/[id]/page.tsx +147 -0
- package/src/admin/routes/coupons/create/page.tsx +49 -0
- package/src/admin/routes/coupons/page.tsx +15 -0
- package/src/admin/routes/flash-sales/[id]/page.tsx +2 -11
- package/src/admin/routes/flash-sales/create/page.tsx +0 -6
- package/src/admin/widgets/campaign-detail-widget.tsx +33 -26
- package/src/api/admin/buy-x-get-y/[id]/route.ts +11 -15
- package/src/api/admin/coupons/[id]/route.ts +98 -0
- package/src/api/admin/coupons/route.ts +109 -0
- package/src/api/middlewares.ts +34 -0
- package/src/api/store/campaigns/route.ts +107 -24
- package/src/api/store/coupons/public/route.ts +165 -0
- package/src/api/store/customers/me/coupons/route.ts +244 -0
- package/src/modules/custom-campaigns/migrations/Migration20251024000000.ts +1 -1
- package/src/modules/custom-campaigns/migrations/Migration20251025000000.ts +1 -1
- package/src/modules/custom-campaigns/migrations/Migration20251101000000.ts +21 -0
- package/src/modules/custom-campaigns/types/campaign-type.enum.ts +1 -0
- package/src/workflows/custom-campaign/createCouponCampaignWorkflow.ts +176 -0
- package/src/workflows/custom-campaign/updateCouponCampaignWorkflow.ts +105 -0
- package/src/workflows/index.ts +3 -1
- package/src/admin/widgets/campaign-stats-widget.tsx +0 -238
|
@@ -6,7 +6,6 @@ const icons = require("@medusajs/icons");
|
|
|
6
6
|
const React = require("react");
|
|
7
7
|
const reactRouterDom = require("react-router-dom");
|
|
8
8
|
const dayjs = require("dayjs");
|
|
9
|
-
const reactQuery = require("@tanstack/react-query");
|
|
10
9
|
const axios = require("axios");
|
|
11
10
|
const z = require("zod");
|
|
12
11
|
const lodash = require("lodash");
|
|
@@ -17,10 +16,14 @@ const dayjs__default = /* @__PURE__ */ _interopDefault(dayjs);
|
|
|
17
16
|
const axios__default = /* @__PURE__ */ _interopDefault(axios);
|
|
18
17
|
const z__default = /* @__PURE__ */ _interopDefault(z);
|
|
19
18
|
const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
|
|
20
|
-
const CampaignDetailWidget = ({
|
|
19
|
+
const CampaignDetailWidget = ({
|
|
20
|
+
data: promotion
|
|
21
|
+
}) => {
|
|
21
22
|
const navigate = reactRouterDom.useNavigate();
|
|
22
23
|
const [campaign, setCampaign] = React.useState(null);
|
|
23
|
-
const [campaignDetail, setCampaignDetail] = React.useState(
|
|
24
|
+
const [campaignDetail, setCampaignDetail] = React.useState(
|
|
25
|
+
null
|
|
26
|
+
);
|
|
24
27
|
const [loading, setLoading] = React.useState(true);
|
|
25
28
|
const [error, setError] = React.useState(null);
|
|
26
29
|
React.useEffect(() => {
|
|
@@ -117,11 +120,21 @@ const CampaignDetailWidget = ({ data: promotion }) => {
|
|
|
117
120
|
] }),
|
|
118
121
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2 mt-3 pt-3 border-t", children: [
|
|
119
122
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
120
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
123
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
124
|
+
icons.PhotoSolid,
|
|
125
|
+
{
|
|
126
|
+
className: `h-4 w-4 ${hasImages ? "text-green-500" : "text-ui-fg-muted"}`
|
|
127
|
+
}
|
|
128
|
+
),
|
|
121
129
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs", children: hasImages ? "Images added" : "No images" })
|
|
122
130
|
] }),
|
|
123
131
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
124
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
132
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
133
|
+
icons.PencilSquare,
|
|
134
|
+
{
|
|
135
|
+
className: `h-4 w-4 ${hasContent ? "text-green-500" : "text-ui-fg-muted"}`
|
|
136
|
+
}
|
|
137
|
+
),
|
|
125
138
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs", children: hasContent ? "Content added" : "No content" })
|
|
126
139
|
] })
|
|
127
140
|
] })
|
|
@@ -156,18 +169,10 @@ const CampaignDetailWidget = ({ data: promotion }) => {
|
|
|
156
169
|
] }),
|
|
157
170
|
!hasCampaignDetail && /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "info", className: "mb-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm", children: "No campaign details added yet. Add images, content, and SEO to enhance this campaign." }) }),
|
|
158
171
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
159
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
size: "small",
|
|
164
|
-
onClick: handleEditCampaign,
|
|
165
|
-
children: [
|
|
166
|
-
/* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "mr-1" }),
|
|
167
|
-
"Edit Campaign"
|
|
168
|
-
]
|
|
169
|
-
}
|
|
170
|
-
),
|
|
172
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "secondary", size: "small", onClick: handleEditCampaign, children: [
|
|
173
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "mr-1" }),
|
|
174
|
+
"Edit Campaign"
|
|
175
|
+
] }),
|
|
171
176
|
hasCampaignDetail && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
172
177
|
ui.Button,
|
|
173
178
|
{
|
|
@@ -196,185 +201,142 @@ const CampaignDetailWidget = ({ data: promotion }) => {
|
|
|
196
201
|
adminSdk.defineWidgetConfig({
|
|
197
202
|
zone: "promotion.details.side.after"
|
|
198
203
|
});
|
|
199
|
-
const
|
|
200
|
-
const [
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
complete: 0
|
|
207
|
-
});
|
|
208
|
-
const [loading, setLoading] = React.useState(true);
|
|
209
|
-
React.useEffect(() => {
|
|
210
|
-
fetchStats();
|
|
211
|
-
}, []);
|
|
212
|
-
const fetchStats = async () => {
|
|
213
|
-
setLoading(true);
|
|
204
|
+
const useCoupons = ({ limit, offset }) => {
|
|
205
|
+
const [data, setData] = React.useState(null);
|
|
206
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
207
|
+
const [error, setError] = React.useState(null);
|
|
208
|
+
const fetchCoupons = async () => {
|
|
209
|
+
setIsLoading(true);
|
|
210
|
+
setError(null);
|
|
214
211
|
try {
|
|
215
|
-
const response = await
|
|
216
|
-
|
|
212
|
+
const response = await axios__default.default.get("/admin/coupons", {
|
|
213
|
+
params: { limit, offset }
|
|
217
214
|
});
|
|
218
|
-
|
|
219
|
-
const data = await response.json();
|
|
220
|
-
const campaigns = data.campaigns || [];
|
|
221
|
-
const now = /* @__PURE__ */ new Date();
|
|
222
|
-
let active = 0;
|
|
223
|
-
let inactive = 0;
|
|
224
|
-
let withImages = 0;
|
|
225
|
-
let withContent = 0;
|
|
226
|
-
let complete = 0;
|
|
227
|
-
campaigns.forEach((campaign) => {
|
|
228
|
-
if (campaign.ends_at && new Date(campaign.ends_at) > now) {
|
|
229
|
-
active++;
|
|
230
|
-
} else {
|
|
231
|
-
inactive++;
|
|
232
|
-
}
|
|
233
|
-
if (campaign.campaign_detail) {
|
|
234
|
-
if (campaign.campaign_detail.image_url || campaign.campaign_detail.thumbnail_url) {
|
|
235
|
-
withImages++;
|
|
236
|
-
}
|
|
237
|
-
if (campaign.campaign_detail.detail_content) {
|
|
238
|
-
withContent++;
|
|
239
|
-
}
|
|
240
|
-
if ((campaign.campaign_detail.image_url || campaign.campaign_detail.thumbnail_url) && campaign.campaign_detail.detail_content) {
|
|
241
|
-
complete++;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
setStats({
|
|
246
|
-
total: campaigns.length,
|
|
247
|
-
active,
|
|
248
|
-
inactive,
|
|
249
|
-
with_images: withImages,
|
|
250
|
-
with_content: withContent,
|
|
251
|
-
complete
|
|
252
|
-
});
|
|
253
|
-
}
|
|
215
|
+
setData(response.data);
|
|
254
216
|
} catch (err) {
|
|
255
|
-
|
|
217
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch coupons"));
|
|
256
218
|
} finally {
|
|
257
|
-
|
|
219
|
+
setIsLoading(false);
|
|
258
220
|
}
|
|
259
221
|
};
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs text-gray-600", children: "Inactive" }),
|
|
293
|
-
/* @__PURE__ */ jsxRuntime.jsx(icons.ClockSolid, { className: "h-4 w-4 text-gray-500" })
|
|
294
|
-
] }),
|
|
295
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-2xl font-bold text-gray-600", children: stats.inactive })
|
|
296
|
-
] })
|
|
297
|
-
] }),
|
|
298
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 mb-6", children: [
|
|
299
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between p-3 rounded-lg bg-ui-bg-subtle", children: [
|
|
300
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
301
|
-
/* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "h-4 w-4 text-blue-600" }),
|
|
302
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm", children: "With Images" })
|
|
303
|
-
] }),
|
|
304
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
305
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm font-medium", children: stats.with_images }),
|
|
306
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "xsmall", color: "blue", children: [
|
|
307
|
-
stats.total > 0 ? Math.round(stats.with_images / stats.total * 100) : 0,
|
|
308
|
-
"%"
|
|
309
|
-
] })
|
|
310
|
-
] })
|
|
311
|
-
] }),
|
|
312
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between p-3 rounded-lg bg-ui-bg-subtle", children: [
|
|
313
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
314
|
-
/* @__PURE__ */ jsxRuntime.jsx(icons.CheckCircleSolid, { className: "h-4 w-4 text-green-600" }),
|
|
315
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm", children: "With Content" })
|
|
316
|
-
] }),
|
|
317
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
318
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm font-medium", children: stats.with_content }),
|
|
319
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "xsmall", color: "green", children: [
|
|
320
|
-
stats.total > 0 ? Math.round(stats.with_content / stats.total * 100) : 0,
|
|
321
|
-
"%"
|
|
322
|
-
] })
|
|
323
|
-
] })
|
|
324
|
-
] }),
|
|
325
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between p-3 rounded-lg bg-ui-bg-subtle", children: [
|
|
326
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
327
|
-
/* @__PURE__ */ jsxRuntime.jsx(icons.Sparkles, { className: "h-4 w-4 text-purple-600" }),
|
|
328
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm", children: "Complete Details" })
|
|
329
|
-
] }),
|
|
330
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
331
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm font-medium", children: stats.complete }),
|
|
332
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "xsmall", color: "purple", children: [
|
|
333
|
-
completionRate,
|
|
334
|
-
"%"
|
|
335
|
-
] })
|
|
336
|
-
] })
|
|
337
|
-
] })
|
|
338
|
-
] }),
|
|
339
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-4 border-t", children: [
|
|
340
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
341
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs text-ui-fg-subtle", children: "Campaign Completion" }),
|
|
342
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-xs font-medium", children: [
|
|
343
|
-
completionRate,
|
|
344
|
-
"%"
|
|
345
|
-
] })
|
|
346
|
-
] }),
|
|
347
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 bg-ui-bg-subtle rounded-full overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
348
|
-
"div",
|
|
349
|
-
{
|
|
350
|
-
className: "h-full bg-gradient-to-r from-purple-500 to-blue-500 transition-all duration-300",
|
|
351
|
-
style: { width: `${completionRate}%` }
|
|
222
|
+
React.useEffect(() => {
|
|
223
|
+
fetchCoupons();
|
|
224
|
+
}, [limit, offset]);
|
|
225
|
+
return {
|
|
226
|
+
data,
|
|
227
|
+
isLoading,
|
|
228
|
+
error,
|
|
229
|
+
refetch: fetchCoupons
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
const columnHelper$1 = ui.createDataTableColumnHelper();
|
|
233
|
+
const useCouponColumns = () => React.useMemo(
|
|
234
|
+
() => [
|
|
235
|
+
columnHelper$1.accessor("name", {
|
|
236
|
+
header: "Name",
|
|
237
|
+
cell: (info) => info.getValue()
|
|
238
|
+
}),
|
|
239
|
+
columnHelper$1.display({
|
|
240
|
+
id: "code",
|
|
241
|
+
header: "Code",
|
|
242
|
+
cell: (info) => {
|
|
243
|
+
var _a, _b;
|
|
244
|
+
return ((_b = (_a = info.row.original.promotions) == null ? void 0 : _a[0]) == null ? void 0 : _b.code) ?? "-";
|
|
245
|
+
}
|
|
246
|
+
}),
|
|
247
|
+
columnHelper$1.display({
|
|
248
|
+
id: "status",
|
|
249
|
+
header: "Status",
|
|
250
|
+
cell: (info) => {
|
|
251
|
+
const endsAt = info.row.original.ends_at;
|
|
252
|
+
if (!endsAt) {
|
|
253
|
+
return "";
|
|
352
254
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
255
|
+
const isActive = dayjs__default.default(endsAt).isAfter(dayjs__default.default());
|
|
256
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: isActive ? "green" : "grey", children: isActive ? "Active" : "Inactive" });
|
|
257
|
+
}
|
|
258
|
+
}),
|
|
259
|
+
columnHelper$1.accessor("starts_at", {
|
|
260
|
+
header: "Start Date",
|
|
261
|
+
cell: (info) => info.getValue() ? dayjs__default.default(info.getValue()).format("YYYY-MM-DD HH:mm") : ""
|
|
262
|
+
}),
|
|
263
|
+
columnHelper$1.accessor("ends_at", {
|
|
264
|
+
header: "End Date",
|
|
265
|
+
cell: (info) => info.getValue() ? dayjs__default.default(info.getValue()).format("YYYY-MM-DD HH:mm") : ""
|
|
266
|
+
})
|
|
267
|
+
],
|
|
268
|
+
[]
|
|
269
|
+
);
|
|
270
|
+
const CouponPage = () => {
|
|
271
|
+
const navigate = reactRouterDom.useNavigate();
|
|
272
|
+
const limit = 20;
|
|
273
|
+
const [pagination, setPagination] = React.useState({
|
|
274
|
+
pageSize: limit,
|
|
275
|
+
pageIndex: 0
|
|
276
|
+
});
|
|
277
|
+
const offset = pagination.pageIndex * limit;
|
|
278
|
+
const { data } = useCoupons({ limit, offset });
|
|
279
|
+
const columns2 = useCouponColumns();
|
|
280
|
+
const table = ui.useDataTable({
|
|
281
|
+
data: (data == null ? void 0 : data.campaigns) ?? [],
|
|
282
|
+
columns: columns2,
|
|
283
|
+
getRowId: (row) => row.id,
|
|
284
|
+
pagination: {
|
|
285
|
+
state: pagination,
|
|
286
|
+
onPaginationChange: setPagination
|
|
287
|
+
},
|
|
288
|
+
rowCount: (data == null ? void 0 : data.count) ?? 0,
|
|
289
|
+
onRowClick: (_, row) => {
|
|
290
|
+
navigate(`/coupons/${row.id}`);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
294
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
295
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-semibold", children: "Coupons" }),
|
|
296
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => navigate("/coupons/create"), children: "Create Coupon" })
|
|
360
297
|
] }),
|
|
361
|
-
|
|
298
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable, { instance: table, children: [
|
|
299
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {}),
|
|
300
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Pagination, {})
|
|
301
|
+
] })
|
|
362
302
|
] });
|
|
363
303
|
};
|
|
364
|
-
|
|
365
|
-
|
|
304
|
+
const Coupons = () => {
|
|
305
|
+
return /* @__PURE__ */ jsxRuntime.jsx(CouponPage, {});
|
|
306
|
+
};
|
|
307
|
+
const config$3 = adminSdk.defineRouteConfig({
|
|
308
|
+
label: "Coupons",
|
|
309
|
+
icon: icons.Tag
|
|
366
310
|
});
|
|
367
311
|
const useFlashSales = (pagination) => {
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
312
|
+
const [data, setData] = React.useState(null);
|
|
313
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
314
|
+
const [error, setError] = React.useState(null);
|
|
315
|
+
const fetchFlashSales = async () => {
|
|
316
|
+
setIsLoading(true);
|
|
317
|
+
setError(null);
|
|
318
|
+
try {
|
|
319
|
+
const response = await axios__default.default.get("/admin/flash-sales", {
|
|
372
320
|
params: pagination
|
|
373
321
|
});
|
|
374
|
-
|
|
322
|
+
setData(response.data);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
setError(
|
|
325
|
+
err instanceof Error ? err : new Error("Failed to fetch flash sales")
|
|
326
|
+
);
|
|
327
|
+
} finally {
|
|
328
|
+
setIsLoading(false);
|
|
375
329
|
}
|
|
376
|
-
}
|
|
377
|
-
|
|
330
|
+
};
|
|
331
|
+
React.useEffect(() => {
|
|
332
|
+
fetchFlashSales();
|
|
333
|
+
}, [pagination.limit, pagination.offset]);
|
|
334
|
+
return {
|
|
335
|
+
data,
|
|
336
|
+
isLoading,
|
|
337
|
+
error,
|
|
338
|
+
refetch: fetchFlashSales
|
|
339
|
+
};
|
|
378
340
|
};
|
|
379
341
|
const FlashSalePage = () => {
|
|
380
342
|
const navigate = reactRouterDom.useNavigate();
|
|
@@ -452,7 +414,7 @@ const FlashSalePage = () => {
|
|
|
452
414
|
const FlashSale = () => {
|
|
453
415
|
return /* @__PURE__ */ jsxRuntime.jsx(FlashSalePage, {});
|
|
454
416
|
};
|
|
455
|
-
const config$
|
|
417
|
+
const config$2 = adminSdk.defineRouteConfig({
|
|
456
418
|
label: "Flash Sale",
|
|
457
419
|
icon: icons.Sparkles
|
|
458
420
|
});
|
|
@@ -2337,122 +2299,64 @@ var n = function(e2, o2) {
|
|
|
2337
2299
|
}
|
|
2338
2300
|
};
|
|
2339
2301
|
};
|
|
2340
|
-
const
|
|
2341
|
-
baseUrl: "/",
|
|
2342
|
-
debug: false,
|
|
2343
|
-
auth: {
|
|
2344
|
-
type: "session"
|
|
2345
|
-
}
|
|
2346
|
-
});
|
|
2347
|
-
const columnHelper = ui.createDataTableColumnHelper();
|
|
2348
|
-
const columns = [
|
|
2349
|
-
columnHelper.accessor("title", {
|
|
2350
|
-
header: "Title",
|
|
2351
|
-
cell: (info) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
2352
|
-
}),
|
|
2353
|
-
columnHelper.accessor("description", {
|
|
2354
|
-
header: "Description",
|
|
2355
|
-
cell: (info) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
2356
|
-
})
|
|
2357
|
-
];
|
|
2358
|
-
const ProductSelector = ({
|
|
2359
|
-
selectedProductIds,
|
|
2360
|
-
onSelectProduct
|
|
2361
|
-
}) => {
|
|
2362
|
-
const [search, setSearch] = React.useState("");
|
|
2363
|
-
const debouncedSearchHandler = lodash.debounce((value) => {
|
|
2364
|
-
setSearch(value);
|
|
2365
|
-
}, 500);
|
|
2366
|
-
const { data: products } = reactQuery.useQuery({
|
|
2367
|
-
queryKey: ["products", selectedProductIds, search],
|
|
2368
|
-
queryFn: () => sdk.admin.product.list({
|
|
2369
|
-
q: search,
|
|
2370
|
-
limit: 100
|
|
2371
|
-
})
|
|
2372
|
-
});
|
|
2373
|
-
const selectableProducts = React.useMemo(() => {
|
|
2374
|
-
var _a;
|
|
2375
|
-
return (_a = products == null ? void 0 : products.products) == null ? void 0 : _a.filter(
|
|
2376
|
-
(product) => !selectedProductIds.includes(product.id)
|
|
2377
|
-
);
|
|
2378
|
-
}, [products == null ? void 0 : products.products, selectedProductIds]);
|
|
2379
|
-
const table = ui.useDataTable({
|
|
2380
|
-
data: selectableProducts || [],
|
|
2381
|
-
columns,
|
|
2382
|
-
getRowId: (product) => product.id,
|
|
2383
|
-
onRowClick: (_, row) => {
|
|
2384
|
-
onSelectProduct == null ? void 0 : onSelectProduct(row.original);
|
|
2385
|
-
}
|
|
2386
|
-
});
|
|
2387
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
2388
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2389
|
-
ui.Input,
|
|
2390
|
-
{
|
|
2391
|
-
className: "text-lg py-2",
|
|
2392
|
-
placeholder: "Search products...",
|
|
2393
|
-
onChange: (e2) => debouncedSearchHandler(e2.target.value)
|
|
2394
|
-
}
|
|
2395
|
-
),
|
|
2396
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { instance: table, children: /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {}) })
|
|
2397
|
-
] });
|
|
2398
|
-
};
|
|
2399
|
-
const customCampaignSchema = z__default.default.object({
|
|
2302
|
+
const couponSchema = z__default.default.object({
|
|
2400
2303
|
name: z__default.default.string().min(1, "Name is required"),
|
|
2401
2304
|
description: z__default.default.string().min(1, "Description is required"),
|
|
2402
|
-
type: z__default.default.literal("
|
|
2305
|
+
type: z__default.default.literal("coupon"),
|
|
2306
|
+
code: z__default.default.string().min(1, "Coupon code is required"),
|
|
2307
|
+
discount_type: z__default.default.enum(["percentage", "fixed"]),
|
|
2308
|
+
discount_value: z__default.default.number().positive("Discount must be positive"),
|
|
2309
|
+
currency_code: z__default.default.string().optional(),
|
|
2403
2310
|
starts_at: z__default.default.string().min(1, "Start date is required"),
|
|
2404
|
-
ends_at: z__default.default.string().min(1, "End date is required")
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2311
|
+
ends_at: z__default.default.string().min(1, "End date is required"),
|
|
2312
|
+
allocation: z__default.default.enum(["each", "total"]).optional(),
|
|
2313
|
+
target_type: z__default.default.enum(["order", "items"]).optional()
|
|
2314
|
+
}).superRefine((data, ctx) => {
|
|
2315
|
+
if (new Date(data.ends_at) < new Date(data.starts_at)) {
|
|
2316
|
+
ctx.addIssue({
|
|
2317
|
+
code: z__default.default.ZodIssueCode.custom,
|
|
2318
|
+
path: ["ends_at"],
|
|
2319
|
+
message: "End date must be after start date"
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
if (data.discount_type === "fixed" && !data.currency_code) {
|
|
2323
|
+
ctx.addIssue({
|
|
2324
|
+
code: z__default.default.ZodIssueCode.custom,
|
|
2325
|
+
path: ["currency_code"],
|
|
2326
|
+
message: "Currency is required for fixed discount"
|
|
2327
|
+
});
|
|
2328
|
+
}
|
|
2418
2329
|
});
|
|
2419
|
-
const
|
|
2420
|
-
products: z__default.default.array(campaignProductSchema).min(1, "At least one product is required")
|
|
2421
|
-
}).refine(
|
|
2422
|
-
(data) => new Date(data.starts_at) < new Date(data.ends_at),
|
|
2423
|
-
"End date must be after start date"
|
|
2424
|
-
);
|
|
2425
|
-
const FlashSaleForm = ({
|
|
2330
|
+
const CouponForm = ({
|
|
2426
2331
|
initialData,
|
|
2427
|
-
initialProducts,
|
|
2428
2332
|
onSubmit,
|
|
2429
2333
|
onCancel,
|
|
2430
2334
|
disabled = false
|
|
2431
2335
|
}) => {
|
|
2432
|
-
var _a;
|
|
2433
2336
|
const {
|
|
2434
|
-
control,
|
|
2435
2337
|
register,
|
|
2338
|
+
control,
|
|
2436
2339
|
handleSubmit,
|
|
2437
2340
|
watch,
|
|
2438
2341
|
setValue,
|
|
2439
2342
|
formState: { errors }
|
|
2440
2343
|
} = useForm({
|
|
2441
|
-
resolver: t(
|
|
2344
|
+
resolver: t(couponSchema),
|
|
2442
2345
|
defaultValues: {
|
|
2443
|
-
name: (initialData == null ? void 0 : initialData.name)
|
|
2444
|
-
description: (initialData == null ? void 0 : initialData.description)
|
|
2445
|
-
type: "
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2346
|
+
name: (initialData == null ? void 0 : initialData.name) ?? "",
|
|
2347
|
+
description: (initialData == null ? void 0 : initialData.description) ?? "",
|
|
2348
|
+
type: "coupon",
|
|
2349
|
+
code: (initialData == null ? void 0 : initialData.code) ?? "",
|
|
2350
|
+
discount_type: (initialData == null ? void 0 : initialData.discount_type) ?? "percentage",
|
|
2351
|
+
discount_value: (initialData == null ? void 0 : initialData.discount_value) ?? 0,
|
|
2352
|
+
currency_code: (initialData == null ? void 0 : initialData.currency_code) ?? "",
|
|
2353
|
+
starts_at: (initialData == null ? void 0 : initialData.starts_at) ?? dayjs__default.default().startOf("day").format("YYYY-MM-DDTHH:mm"),
|
|
2354
|
+
ends_at: (initialData == null ? void 0 : initialData.ends_at) ?? dayjs__default.default().endOf("day").format("YYYY-MM-DDTHH:mm"),
|
|
2355
|
+
allocation: (initialData == null ? void 0 : initialData.allocation) ?? "total",
|
|
2356
|
+
target_type: (initialData == null ? void 0 : initialData.target_type) ?? "order"
|
|
2449
2357
|
}
|
|
2450
2358
|
});
|
|
2451
|
-
const
|
|
2452
|
-
control,
|
|
2453
|
-
name: "products"
|
|
2454
|
-
});
|
|
2455
|
-
const [openProductModal, setOpenProductModal] = React.useState(false);
|
|
2359
|
+
const discountType = watch("discount_type");
|
|
2456
2360
|
const startsAt = watch("starts_at");
|
|
2457
2361
|
const endsAt = watch("ends_at");
|
|
2458
2362
|
const handleDateTimeChange = (field, type, value) => {
|
|
@@ -2469,42 +2373,34 @@ const FlashSaleForm = ({
|
|
|
2469
2373
|
onSubmit(data);
|
|
2470
2374
|
};
|
|
2471
2375
|
const onFormError = () => {
|
|
2472
|
-
const errorMessages = Object.
|
|
2473
|
-
|
|
2474
|
-
if (key === "products" && Array.isArray(value)) {
|
|
2475
|
-
return value.map(
|
|
2476
|
-
(item, index) => item ? `Product ${index + 1}: ${Object.values(item).join(", ")}` : ""
|
|
2477
|
-
).filter(Boolean).join(", ");
|
|
2478
|
-
}
|
|
2479
|
-
return (value == null ? void 0 : value.message) || ((_a2 = value == null ? void 0 : value.root) == null ? void 0 : _a2.message);
|
|
2480
|
-
}).filter(Boolean).join(", ");
|
|
2481
|
-
ui.toast.error("Invalid data", {
|
|
2376
|
+
const errorMessages = Object.values(errors).map((err) => err == null ? void 0 : err.message).filter(Boolean).join(", ");
|
|
2377
|
+
ui.toast.error("Invalid coupon data", {
|
|
2482
2378
|
description: errorMessages
|
|
2483
2379
|
});
|
|
2484
2380
|
};
|
|
2485
2381
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
2486
2382
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2487
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-semibold", children: "
|
|
2383
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-semibold", children: "Coupon" }),
|
|
2488
2384
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "transparent", onClick: onCancel, children: "Cancel" })
|
|
2489
2385
|
] }),
|
|
2490
2386
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2491
2387
|
"form",
|
|
2492
2388
|
{
|
|
2493
2389
|
onSubmit: handleSubmit(onFormSubmit, onFormError),
|
|
2494
|
-
className: "space-y-
|
|
2390
|
+
className: "space-y-6 my-8",
|
|
2495
2391
|
children: [
|
|
2496
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2392
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2497
2393
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Name" }),
|
|
2498
2394
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...register("name"), disabled }),
|
|
2499
2395
|
errors.name && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.name.message })
|
|
2500
2396
|
] }),
|
|
2501
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2397
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2502
2398
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Description" }),
|
|
2503
2399
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...register("description"), disabled }),
|
|
2504
2400
|
errors.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
|
|
2505
2401
|
] }),
|
|
2506
2402
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2507
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2403
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2508
2404
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Start Date" }),
|
|
2509
2405
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
2510
2406
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2524,13 +2420,13 @@ const FlashSaleForm = ({
|
|
|
2524
2420
|
value: startsAt ? dayjs__default.default(startsAt).format("HH:mm") : "",
|
|
2525
2421
|
onChange: (e2) => handleDateTimeChange("starts_at", "time", e2.target.value),
|
|
2526
2422
|
disabled,
|
|
2527
|
-
className: "
|
|
2423
|
+
className: "flex-1"
|
|
2528
2424
|
}
|
|
2529
2425
|
)
|
|
2530
2426
|
] }),
|
|
2531
2427
|
errors.starts_at && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
|
|
2532
2428
|
] }),
|
|
2533
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2429
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2534
2430
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "End Date" }),
|
|
2535
2431
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
2536
2432
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2550,152 +2446,163 @@ const FlashSaleForm = ({
|
|
|
2550
2446
|
value: endsAt ? dayjs__default.default(endsAt).format("HH:mm") : "",
|
|
2551
2447
|
onChange: (e2) => handleDateTimeChange("ends_at", "time", e2.target.value),
|
|
2552
2448
|
disabled,
|
|
2553
|
-
className: "
|
|
2449
|
+
className: "flex-1"
|
|
2554
2450
|
}
|
|
2555
2451
|
)
|
|
2556
2452
|
] }),
|
|
2557
2453
|
errors.ends_at && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
|
|
2558
2454
|
] })
|
|
2559
2455
|
] }),
|
|
2560
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "
|
|
2561
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2562
|
-
|
|
2563
|
-
ui.
|
|
2564
|
-
{
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
children: "Add Product"
|
|
2570
|
-
}
|
|
2571
|
-
)
|
|
2572
|
-
] }),
|
|
2573
|
-
((_a = errors.products) == null ? void 0 : _a.root) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm", children: errors.products.root.message }),
|
|
2574
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
2575
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
2576
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
|
|
2577
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Discount Type" }),
|
|
2578
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Discount Value" }),
|
|
2579
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Limit" }),
|
|
2580
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Max Qty per Order" }),
|
|
2581
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Actions" })
|
|
2582
|
-
] }) }),
|
|
2583
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: fields.map((field, index) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
2584
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: field.product.title }),
|
|
2585
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2456
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2457
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2458
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Coupon Code" }),
|
|
2459
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...register("code"), disabled }),
|
|
2460
|
+
errors.code && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.code.message })
|
|
2461
|
+
] }),
|
|
2462
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2463
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Discount Type" }),
|
|
2464
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2586
2465
|
Controller,
|
|
2587
2466
|
{
|
|
2588
|
-
name:
|
|
2467
|
+
name: "discount_type",
|
|
2589
2468
|
control,
|
|
2590
|
-
render: ({ field
|
|
2469
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2591
2470
|
ui.Select,
|
|
2592
2471
|
{
|
|
2593
|
-
value:
|
|
2594
|
-
onValueChange:
|
|
2472
|
+
value: field.value,
|
|
2473
|
+
onValueChange: field.onChange,
|
|
2595
2474
|
disabled,
|
|
2596
2475
|
children: [
|
|
2597
2476
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select discount type" }) }),
|
|
2598
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2477
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
2478
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "percentage", children: "Percentage" }),
|
|
2479
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "fixed", children: "Fixed Amount" })
|
|
2480
|
+
] })
|
|
2599
2481
|
]
|
|
2600
2482
|
}
|
|
2601
2483
|
)
|
|
2602
2484
|
}
|
|
2603
|
-
)
|
|
2604
|
-
|
|
2485
|
+
)
|
|
2486
|
+
] })
|
|
2487
|
+
] }),
|
|
2488
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2489
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2490
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Discount Value" }),
|
|
2491
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2605
2492
|
Controller,
|
|
2606
2493
|
{
|
|
2607
|
-
name:
|
|
2494
|
+
name: "discount_value",
|
|
2608
2495
|
control,
|
|
2609
|
-
render: ({ field
|
|
2496
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2610
2497
|
ui.Input,
|
|
2611
2498
|
{
|
|
2612
2499
|
type: "number",
|
|
2613
|
-
value:
|
|
2614
|
-
|
|
2500
|
+
value: field.value,
|
|
2501
|
+
min: 0,
|
|
2502
|
+
step: field.value % 1 === 0 ? 1 : 0.01,
|
|
2503
|
+
onChange: (e2) => field.onChange(Number(e2.target.value)),
|
|
2615
2504
|
disabled
|
|
2616
2505
|
}
|
|
2617
2506
|
)
|
|
2618
2507
|
}
|
|
2619
|
-
)
|
|
2620
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2508
|
+
),
|
|
2509
|
+
errors.discount_value && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.discount_value.message })
|
|
2510
|
+
] }),
|
|
2511
|
+
discountType === "fixed" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2512
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Currency Code" }),
|
|
2513
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...register("currency_code"), disabled }),
|
|
2514
|
+
errors.currency_code && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.currency_code.message })
|
|
2515
|
+
] })
|
|
2516
|
+
] }),
|
|
2517
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2518
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2519
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Allocation" }),
|
|
2520
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2621
2521
|
Controller,
|
|
2622
2522
|
{
|
|
2623
|
-
name:
|
|
2523
|
+
name: "allocation",
|
|
2624
2524
|
control,
|
|
2625
|
-
render: ({ field
|
|
2626
|
-
ui.
|
|
2525
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2526
|
+
ui.Select,
|
|
2627
2527
|
{
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2528
|
+
value: field.value,
|
|
2529
|
+
onValueChange: field.onChange,
|
|
2530
|
+
disabled,
|
|
2531
|
+
children: [
|
|
2532
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select allocation" }) }),
|
|
2533
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
2534
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "total", children: "Order Total" }),
|
|
2535
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "each", children: "Each Item" })
|
|
2536
|
+
] })
|
|
2537
|
+
]
|
|
2632
2538
|
}
|
|
2633
2539
|
)
|
|
2634
2540
|
}
|
|
2635
|
-
)
|
|
2636
|
-
|
|
2541
|
+
)
|
|
2542
|
+
] }),
|
|
2543
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2544
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Target Type" }),
|
|
2545
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2637
2546
|
Controller,
|
|
2638
2547
|
{
|
|
2639
|
-
name:
|
|
2548
|
+
name: "target_type",
|
|
2640
2549
|
control,
|
|
2641
|
-
render: ({ field
|
|
2642
|
-
ui.
|
|
2550
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2551
|
+
ui.Select,
|
|
2643
2552
|
{
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2553
|
+
value: field.value,
|
|
2554
|
+
onValueChange: field.onChange,
|
|
2555
|
+
disabled,
|
|
2556
|
+
children: [
|
|
2557
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select target" }) }),
|
|
2558
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
2559
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "order", children: "Order" }),
|
|
2560
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "items", children: "Items" })
|
|
2561
|
+
] })
|
|
2562
|
+
]
|
|
2648
2563
|
}
|
|
2649
2564
|
)
|
|
2650
2565
|
}
|
|
2651
|
-
)
|
|
2652
|
-
|
|
2653
|
-
ui.Button,
|
|
2654
|
-
{
|
|
2655
|
-
type: "button",
|
|
2656
|
-
variant: "danger",
|
|
2657
|
-
onClick: () => remove(index),
|
|
2658
|
-
disabled,
|
|
2659
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
|
|
2660
|
-
}
|
|
2661
|
-
) })
|
|
2662
|
-
] }, field.id)) })
|
|
2566
|
+
)
|
|
2567
|
+
] })
|
|
2663
2568
|
] }),
|
|
2664
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2569
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
2570
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", type: "button", onClick: onCancel, children: "Cancel" }),
|
|
2571
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled, children: "Save Coupon" })
|
|
2572
|
+
] })
|
|
2665
2573
|
]
|
|
2666
2574
|
}
|
|
2667
|
-
)
|
|
2668
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal, { open: openProductModal, onOpenChange: setOpenProductModal, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Content, { children: [
|
|
2669
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Header, { title: "Add Product" }),
|
|
2670
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Body, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2671
|
-
ProductSelector,
|
|
2672
|
-
{
|
|
2673
|
-
selectedProductIds: fields.map((f2) => f2.product.id),
|
|
2674
|
-
onSelectProduct: (product) => {
|
|
2675
|
-
append({
|
|
2676
|
-
product,
|
|
2677
|
-
discountType: "percentage",
|
|
2678
|
-
discountValue: 10,
|
|
2679
|
-
limit: 10,
|
|
2680
|
-
maxQty: 1
|
|
2681
|
-
});
|
|
2682
|
-
setOpenProductModal(false);
|
|
2683
|
-
}
|
|
2684
|
-
}
|
|
2685
|
-
) })
|
|
2686
|
-
] }) })
|
|
2575
|
+
)
|
|
2687
2576
|
] });
|
|
2688
2577
|
};
|
|
2689
|
-
const
|
|
2690
|
-
const
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2578
|
+
const useCouponById = (id) => {
|
|
2579
|
+
const [data, setData] = React.useState(null);
|
|
2580
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
2581
|
+
const [error, setError] = React.useState(null);
|
|
2582
|
+
const fetchCoupon = async () => {
|
|
2583
|
+
if (!id) {
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
setIsLoading(true);
|
|
2587
|
+
setError(null);
|
|
2588
|
+
try {
|
|
2589
|
+
const response = await axios__default.default.get(`/admin/coupons/${id}`);
|
|
2590
|
+
setData(response.data);
|
|
2591
|
+
} catch (err) {
|
|
2592
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch coupon"));
|
|
2593
|
+
} finally {
|
|
2594
|
+
setIsLoading(false);
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
React.useEffect(() => {
|
|
2598
|
+
fetchCoupon();
|
|
2599
|
+
}, [id]);
|
|
2600
|
+
return {
|
|
2601
|
+
data,
|
|
2602
|
+
isLoading,
|
|
2603
|
+
error,
|
|
2604
|
+
refetch: fetchCoupon
|
|
2605
|
+
};
|
|
2699
2606
|
};
|
|
2700
2607
|
const MarkdownEditor = ({
|
|
2701
2608
|
label,
|
|
@@ -3539,172 +3446,698 @@ const CampaignDetailForm = ({
|
|
|
3539
3446
|
)
|
|
3540
3447
|
] });
|
|
3541
3448
|
};
|
|
3542
|
-
const
|
|
3449
|
+
const CouponDetail = () => {
|
|
3450
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3543
3451
|
const { id } = reactRouterDom.useParams();
|
|
3544
3452
|
const navigate = reactRouterDom.useNavigate();
|
|
3545
|
-
const queryClient = reactQuery.useQueryClient();
|
|
3546
|
-
const { data, isLoading } = useFlashSaleById(id || "");
|
|
3547
3453
|
const [isEditing, setIsEditing] = React.useState(false);
|
|
3548
|
-
|
|
3549
|
-
|
|
3454
|
+
const { data, isLoading, refetch } = useCouponById(id ?? "");
|
|
3455
|
+
if (isLoading) {
|
|
3456
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent" }) });
|
|
3457
|
+
}
|
|
3458
|
+
if (!(data == null ? void 0 : data.campaign)) {
|
|
3459
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Coupon not found" }) });
|
|
3460
|
+
}
|
|
3461
|
+
const campaign = data.campaign;
|
|
3462
|
+
const promotion = (_a = campaign.promotions) == null ? void 0 : _a[0];
|
|
3463
|
+
const initialValues = {
|
|
3464
|
+
name: campaign.name ?? "",
|
|
3465
|
+
description: campaign.description ?? "",
|
|
3466
|
+
type: "coupon",
|
|
3467
|
+
code: (promotion == null ? void 0 : promotion.code) ?? "",
|
|
3468
|
+
discount_type: ((_b = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _b.type) ?? "percentage",
|
|
3469
|
+
discount_value: Number(((_c = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _c.value) ?? 0),
|
|
3470
|
+
currency_code: ((_d = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _d.currency_code) ?? void 0,
|
|
3471
|
+
starts_at: campaign.starts_at ? dayjs__default.default(campaign.starts_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
3472
|
+
ends_at: campaign.ends_at ? dayjs__default.default(campaign.ends_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
3473
|
+
allocation: ((_e = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _e.allocation) ?? "total",
|
|
3474
|
+
target_type: ((_f = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _f.target_type) ?? "order"
|
|
3475
|
+
};
|
|
3476
|
+
const handleSubmit = async (formData) => {
|
|
3477
|
+
var _a2, _b2;
|
|
3478
|
+
if (!id) {
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3550
3481
|
try {
|
|
3551
|
-
await axios__default.default.put(`/admin/
|
|
3552
|
-
...
|
|
3553
|
-
starts_at: new Date(
|
|
3554
|
-
ends_at: new Date(
|
|
3555
|
-
|
|
3556
|
-
ui.toast.success("Flash sale updated successfully");
|
|
3557
|
-
queryClient.invalidateQueries({
|
|
3558
|
-
exact: false,
|
|
3559
|
-
predicate(query) {
|
|
3560
|
-
return ["flash-sales", "flash-sale"].includes(
|
|
3561
|
-
query.queryKey[0]
|
|
3562
|
-
);
|
|
3563
|
-
}
|
|
3482
|
+
await axios__default.default.put(`/admin/coupons/${id}`, {
|
|
3483
|
+
...formData,
|
|
3484
|
+
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
3485
|
+
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
3486
|
+
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
3564
3487
|
});
|
|
3565
|
-
|
|
3488
|
+
ui.toast.success("Coupon updated successfully");
|
|
3489
|
+
setIsEditing(false);
|
|
3490
|
+
refetch();
|
|
3566
3491
|
} catch (error) {
|
|
3567
|
-
let message;
|
|
3492
|
+
let message = "Failed to update coupon";
|
|
3568
3493
|
if (axios__default.default.isAxiosError(error)) {
|
|
3569
|
-
|
|
3570
|
-
message = (_a = error.response) == null ? void 0 : _a.data.message;
|
|
3494
|
+
message = ((_b2 = (_a2 = error.response) == null ? void 0 : _a2.data) == null ? void 0 : _b2.message) ?? message;
|
|
3571
3495
|
} else if (error instanceof Error) {
|
|
3572
3496
|
message = error.message;
|
|
3573
|
-
} else {
|
|
3574
|
-
message = "Failed to update flash sale";
|
|
3575
3497
|
}
|
|
3576
|
-
ui.toast.error("Failed to update
|
|
3498
|
+
ui.toast.error("Failed to update coupon", {
|
|
3577
3499
|
description: message
|
|
3578
3500
|
});
|
|
3579
3501
|
}
|
|
3580
|
-
}
|
|
3581
|
-
if (isLoading) {
|
|
3582
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent" }) });
|
|
3583
|
-
}
|
|
3584
|
-
if (!data) {
|
|
3585
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Flash sale not found" }) });
|
|
3586
|
-
}
|
|
3587
|
-
const campaignData = {
|
|
3588
|
-
name: data.name || "",
|
|
3589
|
-
description: data.description || "",
|
|
3590
|
-
type: "flash-sale",
|
|
3591
|
-
starts_at: data.starts_at,
|
|
3592
|
-
ends_at: data.ends_at
|
|
3593
3502
|
};
|
|
3594
3503
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
3595
3504
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
3596
3505
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3597
|
-
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold mb-1", children:
|
|
3598
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle", children: "Manage
|
|
3506
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold mb-1", children: campaign.name }),
|
|
3507
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle", children: "Manage coupon configuration and content" })
|
|
3599
3508
|
] }),
|
|
3600
3509
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
3601
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/
|
|
3510
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/coupons"), children: "Back to Coupons" }),
|
|
3602
3511
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
|
|
3603
3512
|
] })
|
|
3604
3513
|
] }),
|
|
3605
3514
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { defaultValue: "settings", children: [
|
|
3606
3515
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
|
|
3607
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "settings", children: "
|
|
3608
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "details", children: "
|
|
3516
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "settings", children: "Coupon Settings" }),
|
|
3517
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "details", children: "Coupon Details" })
|
|
3609
3518
|
] }),
|
|
3610
3519
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3611
|
-
|
|
3520
|
+
CouponForm,
|
|
3612
3521
|
{
|
|
3613
|
-
initialData:
|
|
3614
|
-
initialProducts: new Map(
|
|
3615
|
-
data.products.map((product) => [product.product.id, product])
|
|
3616
|
-
),
|
|
3522
|
+
initialData: initialValues,
|
|
3617
3523
|
onSubmit: handleSubmit,
|
|
3618
|
-
onCancel: () => navigate("/
|
|
3524
|
+
onCancel: () => navigate("/coupons"),
|
|
3619
3525
|
disabled: !isEditing
|
|
3620
3526
|
}
|
|
3621
3527
|
) }),
|
|
3622
3528
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3623
3529
|
CampaignDetailForm,
|
|
3624
3530
|
{
|
|
3625
|
-
campaignId: id
|
|
3626
|
-
campaignName:
|
|
3531
|
+
campaignId: campaign.id,
|
|
3532
|
+
campaignName: campaign.name ?? ""
|
|
3627
3533
|
}
|
|
3628
3534
|
) })
|
|
3629
3535
|
] })
|
|
3630
3536
|
] });
|
|
3631
3537
|
};
|
|
3632
|
-
const config = adminSdk.defineRouteConfig({
|
|
3633
|
-
label: "
|
|
3634
|
-
icon: icons.
|
|
3538
|
+
const config$1 = adminSdk.defineRouteConfig({
|
|
3539
|
+
label: "Coupon Detail",
|
|
3540
|
+
icon: icons.Tag
|
|
3635
3541
|
});
|
|
3636
|
-
const
|
|
3542
|
+
const CouponCreate = () => {
|
|
3637
3543
|
const navigate = reactRouterDom.useNavigate();
|
|
3638
|
-
const
|
|
3639
|
-
|
|
3640
|
-
var _a;
|
|
3544
|
+
const handleSubmit = async (formData) => {
|
|
3545
|
+
var _a, _b;
|
|
3641
3546
|
try {
|
|
3642
|
-
await axios__default.default.post("/admin/
|
|
3643
|
-
...
|
|
3644
|
-
starts_at: new Date(
|
|
3645
|
-
ends_at: new Date(
|
|
3646
|
-
|
|
3647
|
-
ui.toast.success("Flash sale created successfully");
|
|
3648
|
-
queryClient.invalidateQueries({
|
|
3649
|
-
exact: false,
|
|
3650
|
-
queryKey: ["flash-sales"]
|
|
3547
|
+
await axios__default.default.post("/admin/coupons", {
|
|
3548
|
+
...formData,
|
|
3549
|
+
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
3550
|
+
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
3551
|
+
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
3651
3552
|
});
|
|
3652
|
-
|
|
3553
|
+
ui.toast.success("Coupon created successfully");
|
|
3554
|
+
navigate("/coupons");
|
|
3653
3555
|
} catch (error) {
|
|
3654
|
-
let message;
|
|
3556
|
+
let message = "Failed to create coupon";
|
|
3655
3557
|
if (axios__default.default.isAxiosError(error)) {
|
|
3656
|
-
|
|
3657
|
-
message = (_a = error.response) == null ? void 0 : _a.data.message;
|
|
3558
|
+
message = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) ?? message;
|
|
3658
3559
|
} else if (error instanceof Error) {
|
|
3659
3560
|
message = error.message;
|
|
3660
|
-
} else {
|
|
3661
|
-
message = "Failed to create flash sale";
|
|
3662
3561
|
}
|
|
3663
|
-
ui.toast.error("Failed to create
|
|
3562
|
+
ui.toast.error("Failed to create coupon", {
|
|
3664
3563
|
description: message
|
|
3665
3564
|
});
|
|
3666
3565
|
}
|
|
3667
|
-
}
|
|
3566
|
+
};
|
|
3668
3567
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3669
|
-
|
|
3568
|
+
CouponForm,
|
|
3670
3569
|
{
|
|
3671
3570
|
onSubmit: handleSubmit,
|
|
3672
|
-
onCancel: () => navigate("/
|
|
3571
|
+
onCancel: () => navigate("/coupons")
|
|
3673
3572
|
}
|
|
3674
3573
|
);
|
|
3675
3574
|
};
|
|
3676
|
-
const
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
{
|
|
3682
|
-
Component: CampaignStatsWidget,
|
|
3683
|
-
zone: ["campaign.list.before"]
|
|
3575
|
+
const sdk = new Medusa__default.default({
|
|
3576
|
+
baseUrl: "/",
|
|
3577
|
+
debug: false,
|
|
3578
|
+
auth: {
|
|
3579
|
+
type: "session"
|
|
3684
3580
|
}
|
|
3685
|
-
|
|
3686
|
-
const
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
const
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3581
|
+
});
|
|
3582
|
+
const columnHelper = ui.createDataTableColumnHelper();
|
|
3583
|
+
const columns = [
|
|
3584
|
+
columnHelper.accessor("title", {
|
|
3585
|
+
header: "Title",
|
|
3586
|
+
cell: (info) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
3587
|
+
}),
|
|
3588
|
+
columnHelper.accessor("description", {
|
|
3589
|
+
header: "Description",
|
|
3590
|
+
cell: (info) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
3591
|
+
})
|
|
3592
|
+
];
|
|
3593
|
+
const ProductSelector = ({
|
|
3594
|
+
selectedProductIds,
|
|
3595
|
+
onSelectProduct
|
|
3596
|
+
}) => {
|
|
3597
|
+
const [search, setSearch] = React.useState("");
|
|
3598
|
+
const debouncedSearchHandler = lodash.debounce((value) => {
|
|
3599
|
+
setSearch(value);
|
|
3600
|
+
}, 500);
|
|
3601
|
+
const [products, setProducts] = React.useState(null);
|
|
3602
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
3603
|
+
React.useEffect(() => {
|
|
3604
|
+
const fetchProducts = async () => {
|
|
3605
|
+
setIsLoading(true);
|
|
3606
|
+
try {
|
|
3607
|
+
const result = await sdk.admin.product.list({
|
|
3608
|
+
q: search,
|
|
3609
|
+
limit: 100
|
|
3610
|
+
});
|
|
3611
|
+
setProducts(result);
|
|
3612
|
+
} catch (error) {
|
|
3613
|
+
console.error("Failed to fetch products:", error);
|
|
3614
|
+
} finally {
|
|
3615
|
+
setIsLoading(false);
|
|
3616
|
+
}
|
|
3617
|
+
};
|
|
3618
|
+
fetchProducts();
|
|
3619
|
+
}, [selectedProductIds, search]);
|
|
3620
|
+
const selectableProducts = React.useMemo(() => {
|
|
3621
|
+
var _a;
|
|
3622
|
+
return (_a = products == null ? void 0 : products.products) == null ? void 0 : _a.filter(
|
|
3623
|
+
(product) => !selectedProductIds.includes(product.id)
|
|
3624
|
+
);
|
|
3625
|
+
}, [products == null ? void 0 : products.products, selectedProductIds]);
|
|
3626
|
+
const table = ui.useDataTable({
|
|
3627
|
+
data: selectableProducts || [],
|
|
3628
|
+
columns,
|
|
3629
|
+
getRowId: (product) => product.id,
|
|
3630
|
+
onRowClick: (_, row) => {
|
|
3631
|
+
onSelectProduct == null ? void 0 : onSelectProduct(row.original);
|
|
3632
|
+
}
|
|
3633
|
+
});
|
|
3634
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
3635
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3636
|
+
ui.Input,
|
|
3637
|
+
{
|
|
3638
|
+
className: "text-lg py-2",
|
|
3639
|
+
placeholder: "Search products...",
|
|
3640
|
+
onChange: (e2) => debouncedSearchHandler(e2.target.value)
|
|
3641
|
+
}
|
|
3642
|
+
),
|
|
3643
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { instance: table, children: /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {}) })
|
|
3644
|
+
] });
|
|
3645
|
+
};
|
|
3646
|
+
const customCampaignSchema = z__default.default.object({
|
|
3647
|
+
name: z__default.default.string().min(1, "Name is required"),
|
|
3648
|
+
description: z__default.default.string().min(1, "Description is required"),
|
|
3649
|
+
type: z__default.default.literal("flash-sale"),
|
|
3650
|
+
starts_at: z__default.default.string().min(1, "Start date is required"),
|
|
3651
|
+
ends_at: z__default.default.string().min(1, "End date is required")
|
|
3652
|
+
});
|
|
3653
|
+
const campaignProductSchema = z__default.default.object({
|
|
3654
|
+
product: z__default.default.object({
|
|
3655
|
+
id: z__default.default.string(),
|
|
3656
|
+
title: z__default.default.string()
|
|
3657
|
+
}),
|
|
3658
|
+
discountType: z__default.default.enum([
|
|
3659
|
+
"percentage"
|
|
3660
|
+
// , "fixed"
|
|
3661
|
+
]),
|
|
3662
|
+
discountValue: z__default.default.number().min(1),
|
|
3663
|
+
limit: z__default.default.number().min(1),
|
|
3664
|
+
maxQty: z__default.default.number().min(1)
|
|
3665
|
+
});
|
|
3666
|
+
const campaignDataSchema = customCampaignSchema.extend({
|
|
3667
|
+
products: z__default.default.array(campaignProductSchema).min(1, "At least one product is required")
|
|
3668
|
+
}).refine(
|
|
3669
|
+
(data) => new Date(data.starts_at) < new Date(data.ends_at),
|
|
3670
|
+
"End date must be after start date"
|
|
3671
|
+
);
|
|
3672
|
+
const FlashSaleForm = ({
|
|
3673
|
+
initialData,
|
|
3674
|
+
initialProducts,
|
|
3675
|
+
onSubmit,
|
|
3676
|
+
onCancel,
|
|
3677
|
+
disabled = false
|
|
3678
|
+
}) => {
|
|
3679
|
+
var _a;
|
|
3680
|
+
const {
|
|
3681
|
+
control,
|
|
3682
|
+
register,
|
|
3683
|
+
handleSubmit,
|
|
3684
|
+
watch,
|
|
3685
|
+
setValue,
|
|
3686
|
+
formState: { errors }
|
|
3687
|
+
} = useForm({
|
|
3688
|
+
resolver: t(campaignDataSchema),
|
|
3689
|
+
defaultValues: {
|
|
3690
|
+
name: (initialData == null ? void 0 : initialData.name) || "",
|
|
3691
|
+
description: (initialData == null ? void 0 : initialData.description) || "",
|
|
3692
|
+
type: "flash-sale",
|
|
3693
|
+
starts_at: (initialData == null ? void 0 : initialData.starts_at) || "",
|
|
3694
|
+
ends_at: (initialData == null ? void 0 : initialData.ends_at) || "",
|
|
3695
|
+
products: initialProducts ? Array.from(initialProducts.values()) : []
|
|
3696
|
+
}
|
|
3697
|
+
});
|
|
3698
|
+
const { fields, append, remove } = useFieldArray({
|
|
3699
|
+
control,
|
|
3700
|
+
name: "products"
|
|
3701
|
+
});
|
|
3702
|
+
const [openProductModal, setOpenProductModal] = React.useState(false);
|
|
3703
|
+
const startsAt = watch("starts_at");
|
|
3704
|
+
const endsAt = watch("ends_at");
|
|
3705
|
+
const handleDateTimeChange = (field, type, value) => {
|
|
3706
|
+
const currentValue = watch(field);
|
|
3707
|
+
if (type === "date") {
|
|
3708
|
+
const time = currentValue ? dayjs__default.default(currentValue).format("HH:mm") : field === "starts_at" ? "00:00" : "23:59";
|
|
3709
|
+
setValue(field, `${value}T${time}`);
|
|
3710
|
+
} else {
|
|
3711
|
+
const date = currentValue ? dayjs__default.default(currentValue).format("YYYY-MM-DD") : dayjs__default.default().format("YYYY-MM-DD");
|
|
3712
|
+
setValue(field, `${date}T${value}`);
|
|
3713
|
+
}
|
|
3714
|
+
};
|
|
3715
|
+
const onFormSubmit = (data) => {
|
|
3716
|
+
onSubmit(data);
|
|
3717
|
+
};
|
|
3718
|
+
const onFormError = () => {
|
|
3719
|
+
const errorMessages = Object.entries(errors).map(([key, value]) => {
|
|
3720
|
+
var _a2;
|
|
3721
|
+
if (key === "products" && Array.isArray(value)) {
|
|
3722
|
+
return value.map(
|
|
3723
|
+
(item, index) => item ? `Product ${index + 1}: ${Object.values(item).join(", ")}` : ""
|
|
3724
|
+
).filter(Boolean).join(", ");
|
|
3725
|
+
}
|
|
3726
|
+
return (value == null ? void 0 : value.message) || ((_a2 = value == null ? void 0 : value.root) == null ? void 0 : _a2.message);
|
|
3727
|
+
}).filter(Boolean).join(", ");
|
|
3728
|
+
ui.toast.error("Invalid data", {
|
|
3729
|
+
description: errorMessages
|
|
3730
|
+
});
|
|
3731
|
+
};
|
|
3732
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
3733
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3734
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-semibold", children: "Flash sale campaign" }),
|
|
3735
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "transparent", onClick: onCancel, children: "Cancel" })
|
|
3736
|
+
] }),
|
|
3737
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3738
|
+
"form",
|
|
3739
|
+
{
|
|
3740
|
+
onSubmit: handleSubmit(onFormSubmit, onFormError),
|
|
3741
|
+
className: "space-y-4 my-8",
|
|
3742
|
+
children: [
|
|
3743
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3744
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Name" }),
|
|
3745
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...register("name"), disabled }),
|
|
3746
|
+
errors.name && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.name.message })
|
|
3747
|
+
] }),
|
|
3748
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3749
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Description" }),
|
|
3750
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...register("description"), disabled }),
|
|
3751
|
+
errors.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
|
|
3752
|
+
] }),
|
|
3753
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
3754
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3755
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Start Date" }),
|
|
3756
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
3757
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3758
|
+
ui.Input,
|
|
3759
|
+
{
|
|
3760
|
+
type: "date",
|
|
3761
|
+
value: startsAt ? dayjs__default.default(startsAt).format("YYYY-MM-DD") : "",
|
|
3762
|
+
onChange: (e2) => handleDateTimeChange("starts_at", "date", e2.target.value),
|
|
3763
|
+
disabled,
|
|
3764
|
+
className: "flex-1"
|
|
3765
|
+
}
|
|
3766
|
+
),
|
|
3767
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3768
|
+
ui.Input,
|
|
3769
|
+
{
|
|
3770
|
+
type: "time",
|
|
3771
|
+
value: startsAt ? dayjs__default.default(startsAt).format("HH:mm") : "",
|
|
3772
|
+
onChange: (e2) => handleDateTimeChange("starts_at", "time", e2.target.value),
|
|
3773
|
+
disabled,
|
|
3774
|
+
className: "w-32"
|
|
3775
|
+
}
|
|
3776
|
+
)
|
|
3777
|
+
] }),
|
|
3778
|
+
errors.starts_at && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
|
|
3779
|
+
] }),
|
|
3780
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3781
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "End Date" }),
|
|
3782
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
3783
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3784
|
+
ui.Input,
|
|
3785
|
+
{
|
|
3786
|
+
type: "date",
|
|
3787
|
+
value: endsAt ? dayjs__default.default(endsAt).format("YYYY-MM-DD") : "",
|
|
3788
|
+
onChange: (e2) => handleDateTimeChange("ends_at", "date", e2.target.value),
|
|
3789
|
+
disabled,
|
|
3790
|
+
className: "flex-1"
|
|
3791
|
+
}
|
|
3792
|
+
),
|
|
3793
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3794
|
+
ui.Input,
|
|
3795
|
+
{
|
|
3796
|
+
type: "time",
|
|
3797
|
+
value: endsAt ? dayjs__default.default(endsAt).format("HH:mm") : "",
|
|
3798
|
+
onChange: (e2) => handleDateTimeChange("ends_at", "time", e2.target.value),
|
|
3799
|
+
disabled,
|
|
3800
|
+
className: "w-32"
|
|
3801
|
+
}
|
|
3802
|
+
)
|
|
3803
|
+
] }),
|
|
3804
|
+
errors.ends_at && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
|
|
3805
|
+
] })
|
|
3806
|
+
] }),
|
|
3807
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
3808
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Products" }),
|
|
3809
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3810
|
+
ui.Button,
|
|
3811
|
+
{
|
|
3812
|
+
type: "button",
|
|
3813
|
+
variant: "secondary",
|
|
3814
|
+
onClick: () => setOpenProductModal(true),
|
|
3815
|
+
disabled,
|
|
3816
|
+
children: "Add Product"
|
|
3817
|
+
}
|
|
3818
|
+
)
|
|
3819
|
+
] }),
|
|
3820
|
+
((_a = errors.products) == null ? void 0 : _a.root) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm", children: errors.products.root.message }),
|
|
3821
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
3822
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
3823
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
|
|
3824
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Discount Type" }),
|
|
3825
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Discount Value" }),
|
|
3826
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Limit" }),
|
|
3827
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Max Qty per Order" }),
|
|
3828
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Actions" })
|
|
3829
|
+
] }) }),
|
|
3830
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: fields.map((field, index) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
3831
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: field.product.title }),
|
|
3832
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3833
|
+
Controller,
|
|
3834
|
+
{
|
|
3835
|
+
name: `products.${index}.discountType`,
|
|
3836
|
+
control,
|
|
3837
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3838
|
+
ui.Select,
|
|
3839
|
+
{
|
|
3840
|
+
value: field2.value,
|
|
3841
|
+
onValueChange: field2.onChange,
|
|
3842
|
+
disabled,
|
|
3843
|
+
children: [
|
|
3844
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select discount type" }) }),
|
|
3845
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "percentage", children: "Percentage" }) })
|
|
3846
|
+
]
|
|
3847
|
+
}
|
|
3848
|
+
)
|
|
3849
|
+
}
|
|
3850
|
+
) }),
|
|
3851
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3852
|
+
Controller,
|
|
3853
|
+
{
|
|
3854
|
+
name: `products.${index}.discountValue`,
|
|
3855
|
+
control,
|
|
3856
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3857
|
+
ui.Input,
|
|
3858
|
+
{
|
|
3859
|
+
type: "number",
|
|
3860
|
+
value: field2.value,
|
|
3861
|
+
onChange: (e2) => field2.onChange(Number(e2.target.value)),
|
|
3862
|
+
disabled
|
|
3863
|
+
}
|
|
3864
|
+
)
|
|
3865
|
+
}
|
|
3866
|
+
) }),
|
|
3867
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3868
|
+
Controller,
|
|
3869
|
+
{
|
|
3870
|
+
name: `products.${index}.limit`,
|
|
3871
|
+
control,
|
|
3872
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3873
|
+
ui.Input,
|
|
3874
|
+
{
|
|
3875
|
+
type: "number",
|
|
3876
|
+
value: field2.value,
|
|
3877
|
+
onChange: (e2) => field2.onChange(Number(e2.target.value)),
|
|
3878
|
+
disabled
|
|
3879
|
+
}
|
|
3880
|
+
)
|
|
3881
|
+
}
|
|
3882
|
+
) }),
|
|
3883
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3884
|
+
Controller,
|
|
3885
|
+
{
|
|
3886
|
+
name: `products.${index}.maxQty`,
|
|
3887
|
+
control,
|
|
3888
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3889
|
+
ui.Input,
|
|
3890
|
+
{
|
|
3891
|
+
type: "number",
|
|
3892
|
+
value: field2.value,
|
|
3893
|
+
onChange: (e2) => field2.onChange(Number(e2.target.value)),
|
|
3894
|
+
disabled
|
|
3895
|
+
}
|
|
3896
|
+
)
|
|
3897
|
+
}
|
|
3898
|
+
) }),
|
|
3899
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3900
|
+
ui.Button,
|
|
3901
|
+
{
|
|
3902
|
+
type: "button",
|
|
3903
|
+
variant: "danger",
|
|
3904
|
+
onClick: () => remove(index),
|
|
3905
|
+
disabled,
|
|
3906
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
|
|
3907
|
+
}
|
|
3908
|
+
) })
|
|
3909
|
+
] }, field.id)) })
|
|
3910
|
+
] }),
|
|
3911
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled, children: "Save" })
|
|
3912
|
+
]
|
|
3913
|
+
}
|
|
3914
|
+
),
|
|
3915
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal, { open: openProductModal, onOpenChange: setOpenProductModal, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Content, { children: [
|
|
3916
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Header, { title: "Add Product" }),
|
|
3917
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Body, { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3918
|
+
ProductSelector,
|
|
3919
|
+
{
|
|
3920
|
+
selectedProductIds: fields.map((f2) => f2.product.id),
|
|
3921
|
+
onSelectProduct: (product) => {
|
|
3922
|
+
append({
|
|
3923
|
+
product,
|
|
3924
|
+
discountType: "percentage",
|
|
3925
|
+
discountValue: 10,
|
|
3926
|
+
limit: 10,
|
|
3927
|
+
maxQty: 1
|
|
3928
|
+
});
|
|
3929
|
+
setOpenProductModal(false);
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
) })
|
|
3933
|
+
] }) })
|
|
3934
|
+
] });
|
|
3935
|
+
};
|
|
3936
|
+
const useFlashSaleById = (id) => {
|
|
3937
|
+
const [data, setData] = React.useState(null);
|
|
3938
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
3939
|
+
const [error, setError] = React.useState(null);
|
|
3940
|
+
const fetchFlashSale = async () => {
|
|
3941
|
+
if (!id) {
|
|
3942
|
+
setIsLoading(false);
|
|
3943
|
+
return;
|
|
3944
|
+
}
|
|
3945
|
+
setIsLoading(true);
|
|
3946
|
+
setError(null);
|
|
3947
|
+
try {
|
|
3948
|
+
const response = await axios__default.default.get(`/admin/flash-sales/${id}`);
|
|
3949
|
+
setData(response.data);
|
|
3950
|
+
} catch (err) {
|
|
3951
|
+
setError(
|
|
3952
|
+
err instanceof Error ? err : new Error("Failed to fetch flash sale")
|
|
3953
|
+
);
|
|
3954
|
+
} finally {
|
|
3955
|
+
setIsLoading(false);
|
|
3956
|
+
}
|
|
3957
|
+
};
|
|
3958
|
+
React.useEffect(() => {
|
|
3959
|
+
fetchFlashSale();
|
|
3960
|
+
}, [id]);
|
|
3961
|
+
return {
|
|
3962
|
+
data,
|
|
3963
|
+
isLoading,
|
|
3964
|
+
error,
|
|
3965
|
+
refetch: fetchFlashSale
|
|
3966
|
+
};
|
|
3967
|
+
};
|
|
3968
|
+
const FlashSaleDetail = () => {
|
|
3969
|
+
const { id } = reactRouterDom.useParams();
|
|
3970
|
+
const navigate = reactRouterDom.useNavigate();
|
|
3971
|
+
const { data, isLoading, refetch } = useFlashSaleById(id || "");
|
|
3972
|
+
const [isEditing, setIsEditing] = React.useState(false);
|
|
3973
|
+
async function handleSubmit(campaignData2) {
|
|
3974
|
+
var _a;
|
|
3975
|
+
try {
|
|
3976
|
+
await axios__default.default.put(`/admin/flash-sales/${id}`, {
|
|
3977
|
+
...campaignData2,
|
|
3978
|
+
starts_at: new Date(campaignData2.starts_at).toUTCString(),
|
|
3979
|
+
ends_at: new Date(campaignData2.ends_at).toUTCString()
|
|
3980
|
+
});
|
|
3981
|
+
ui.toast.success("Flash sale updated successfully");
|
|
3982
|
+
refetch();
|
|
3983
|
+
navigate("/flash-sales");
|
|
3984
|
+
} catch (error) {
|
|
3985
|
+
let message;
|
|
3986
|
+
if (axios__default.default.isAxiosError(error)) {
|
|
3987
|
+
console.log(error);
|
|
3988
|
+
message = (_a = error.response) == null ? void 0 : _a.data.message;
|
|
3989
|
+
} else if (error instanceof Error) {
|
|
3990
|
+
message = error.message;
|
|
3991
|
+
} else {
|
|
3992
|
+
message = "Failed to update flash sale";
|
|
3993
|
+
}
|
|
3994
|
+
ui.toast.error("Failed to update flash sale", {
|
|
3995
|
+
description: message
|
|
3996
|
+
});
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
if (isLoading) {
|
|
4000
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent" }) });
|
|
4001
|
+
}
|
|
4002
|
+
if (!data) {
|
|
4003
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Flash sale not found" }) });
|
|
4004
|
+
}
|
|
4005
|
+
const campaignData = {
|
|
4006
|
+
name: data.name || "",
|
|
4007
|
+
description: data.description || "",
|
|
4008
|
+
type: "flash-sale",
|
|
4009
|
+
starts_at: data.starts_at,
|
|
4010
|
+
ends_at: data.ends_at
|
|
4011
|
+
};
|
|
4012
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
4013
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
4014
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4015
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold mb-1", children: data.name }),
|
|
4016
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle", children: "Manage campaign settings and content" })
|
|
4017
|
+
] }),
|
|
4018
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
4019
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/flash-sales"), children: "Back to Campaigns" }),
|
|
4020
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
|
|
4021
|
+
] })
|
|
4022
|
+
] }),
|
|
4023
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { defaultValue: "settings", children: [
|
|
4024
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
|
|
4025
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "settings", children: "Campaign Settings" }),
|
|
4026
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "details", children: "Campaign Details" })
|
|
4027
|
+
] }),
|
|
4028
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4029
|
+
FlashSaleForm,
|
|
4030
|
+
{
|
|
4031
|
+
initialData: campaignData,
|
|
4032
|
+
initialProducts: new Map(
|
|
4033
|
+
data.products.map((product) => [product.product.id, product])
|
|
4034
|
+
),
|
|
4035
|
+
onSubmit: handleSubmit,
|
|
4036
|
+
onCancel: () => navigate("/flash-sales"),
|
|
4037
|
+
disabled: !isEditing
|
|
4038
|
+
}
|
|
4039
|
+
) }),
|
|
4040
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4041
|
+
CampaignDetailForm,
|
|
4042
|
+
{
|
|
4043
|
+
campaignId: id || "",
|
|
4044
|
+
campaignName: data.name || ""
|
|
4045
|
+
}
|
|
4046
|
+
) })
|
|
4047
|
+
] })
|
|
4048
|
+
] });
|
|
4049
|
+
};
|
|
4050
|
+
const config = adminSdk.defineRouteConfig({
|
|
4051
|
+
label: "Flash Sale Detail",
|
|
4052
|
+
icon: icons.Sparkles
|
|
4053
|
+
});
|
|
4054
|
+
const FlashSaleCreate = () => {
|
|
4055
|
+
const navigate = reactRouterDom.useNavigate();
|
|
4056
|
+
async function handleSubmit(campaignData) {
|
|
4057
|
+
var _a;
|
|
4058
|
+
try {
|
|
4059
|
+
await axios__default.default.post("/admin/flash-sales", {
|
|
4060
|
+
...campaignData,
|
|
4061
|
+
starts_at: new Date(campaignData.starts_at).toUTCString(),
|
|
4062
|
+
ends_at: new Date(campaignData.ends_at).toUTCString()
|
|
4063
|
+
});
|
|
4064
|
+
ui.toast.success("Flash sale created successfully");
|
|
4065
|
+
navigate("/flash-sales");
|
|
4066
|
+
} catch (error) {
|
|
4067
|
+
let message;
|
|
4068
|
+
if (axios__default.default.isAxiosError(error)) {
|
|
4069
|
+
console.log(error);
|
|
4070
|
+
message = (_a = error.response) == null ? void 0 : _a.data.message;
|
|
4071
|
+
} else if (error instanceof Error) {
|
|
4072
|
+
message = error.message;
|
|
4073
|
+
} else {
|
|
4074
|
+
message = "Failed to create flash sale";
|
|
4075
|
+
}
|
|
4076
|
+
ui.toast.error("Failed to create flash sale", {
|
|
4077
|
+
description: message
|
|
4078
|
+
});
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4082
|
+
FlashSaleForm,
|
|
4083
|
+
{
|
|
4084
|
+
onSubmit: handleSubmit,
|
|
4085
|
+
onCancel: () => navigate("/flash-sales")
|
|
4086
|
+
}
|
|
4087
|
+
);
|
|
4088
|
+
};
|
|
4089
|
+
const widgetModule = { widgets: [
|
|
4090
|
+
{
|
|
4091
|
+
Component: CampaignDetailWidget,
|
|
4092
|
+
zone: ["promotion.details.side.after"]
|
|
4093
|
+
}
|
|
4094
|
+
] };
|
|
4095
|
+
const routeModule = {
|
|
4096
|
+
routes: [
|
|
4097
|
+
{
|
|
4098
|
+
Component: Coupons,
|
|
4099
|
+
path: "/coupons"
|
|
4100
|
+
},
|
|
4101
|
+
{
|
|
4102
|
+
Component: FlashSale,
|
|
4103
|
+
path: "/flash-sales"
|
|
4104
|
+
},
|
|
4105
|
+
{
|
|
4106
|
+
Component: CouponDetail,
|
|
4107
|
+
path: "/coupons/:id"
|
|
4108
|
+
},
|
|
4109
|
+
{
|
|
4110
|
+
Component: CouponCreate,
|
|
4111
|
+
path: "/coupons/create"
|
|
4112
|
+
},
|
|
4113
|
+
{
|
|
4114
|
+
Component: FlashSaleDetail,
|
|
4115
|
+
path: "/flash-sales/:id"
|
|
4116
|
+
},
|
|
4117
|
+
{
|
|
4118
|
+
Component: FlashSaleCreate,
|
|
4119
|
+
path: "/flash-sales/create"
|
|
4120
|
+
}
|
|
4121
|
+
]
|
|
4122
|
+
};
|
|
4123
|
+
const menuItemModule = {
|
|
4124
|
+
menuItems: [
|
|
4125
|
+
{
|
|
4126
|
+
label: config$3.label,
|
|
4127
|
+
icon: config$3.icon,
|
|
4128
|
+
path: "/coupons",
|
|
4129
|
+
nested: void 0
|
|
4130
|
+
},
|
|
4131
|
+
{
|
|
4132
|
+
label: config$2.label,
|
|
4133
|
+
icon: config$2.icon,
|
|
4134
|
+
path: "/flash-sales",
|
|
4135
|
+
nested: void 0
|
|
4136
|
+
},
|
|
4137
|
+
{
|
|
4138
|
+
label: config$1.label,
|
|
4139
|
+
icon: config$1.icon,
|
|
4140
|
+
path: "/coupons/:id",
|
|
3708
4141
|
nested: void 0
|
|
3709
4142
|
},
|
|
3710
4143
|
{
|
|
@@ -3719,11 +4152,13 @@ const formModule = { customFields: {} };
|
|
|
3719
4152
|
const displayModule = {
|
|
3720
4153
|
displays: {}
|
|
3721
4154
|
};
|
|
4155
|
+
const i18nModule = { resources: {} };
|
|
3722
4156
|
const plugin = {
|
|
3723
4157
|
widgetModule,
|
|
3724
4158
|
routeModule,
|
|
3725
4159
|
menuItemModule,
|
|
3726
4160
|
formModule,
|
|
3727
|
-
displayModule
|
|
4161
|
+
displayModule,
|
|
4162
|
+
i18nModule
|
|
3728
4163
|
};
|
|
3729
4164
|
module.exports = plugin;
|