@lodashventure/medusa-campaign 1.4.1 → 1.4.3
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 +741 -306
- package/.medusa/server/src/admin/index.mjs +742 -307
- 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,35 @@ 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 useFlashSales = (pagination) => {
|
|
205
|
+
const [data, setData] = React.useState(null);
|
|
206
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
207
|
+
const [error, setError] = React.useState(null);
|
|
208
|
+
const fetchFlashSales = async () => {
|
|
209
|
+
setIsLoading(true);
|
|
210
|
+
setError(null);
|
|
214
211
|
try {
|
|
215
|
-
const response = await
|
|
216
|
-
|
|
212
|
+
const response = await axios__default.default.get("/admin/flash-sales", {
|
|
213
|
+
params: pagination
|
|
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(
|
|
218
|
+
err instanceof Error ? err : new Error("Failed to fetch flash sales")
|
|
219
|
+
);
|
|
256
220
|
} finally {
|
|
257
|
-
|
|
221
|
+
setIsLoading(false);
|
|
258
222
|
}
|
|
259
223
|
};
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
const completionRate = stats.total > 0 ? Math.round(stats.complete / stats.total * 100) : 0;
|
|
270
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "px-6 py-6", children: [
|
|
271
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-6", children: [
|
|
272
|
-
/* @__PURE__ */ jsxRuntime.jsx(icons.Sparkles, { className: "text-ui-fg-subtle" }),
|
|
273
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Campaign Overview" })
|
|
274
|
-
] }),
|
|
275
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-3 gap-4 mb-6", children: [
|
|
276
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 rounded-lg border bg-ui-bg-subtle", children: [
|
|
277
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
278
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs text-ui-fg-subtle", children: "Total" }),
|
|
279
|
-
/* @__PURE__ */ jsxRuntime.jsx(icons.Sparkles, { className: "h-4 w-4 text-ui-fg-subtle" })
|
|
280
|
-
] }),
|
|
281
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-2xl font-bold", children: stats.total })
|
|
282
|
-
] }),
|
|
283
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 rounded-lg border bg-green-50", children: [
|
|
284
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
285
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs text-green-700", children: "Active" }),
|
|
286
|
-
/* @__PURE__ */ jsxRuntime.jsx(icons.CheckCircleSolid, { className: "h-4 w-4 text-green-600" })
|
|
287
|
-
] }),
|
|
288
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-2xl font-bold text-green-700", children: stats.active })
|
|
289
|
-
] }),
|
|
290
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 rounded-lg border bg-gray-50", children: [
|
|
291
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
|
|
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}%` }
|
|
352
|
-
}
|
|
353
|
-
) }),
|
|
354
|
-
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-xs text-ui-fg-muted mt-2", children: [
|
|
355
|
-
stats.complete,
|
|
356
|
-
" of ",
|
|
357
|
-
stats.total,
|
|
358
|
-
" campaigns have complete details"
|
|
359
|
-
] })
|
|
360
|
-
] }),
|
|
361
|
-
completionRate < 50 && stats.total > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 p-3 rounded-lg bg-blue-50 border border-blue-200", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs text-blue-800", children: "💡 Tip: Add images and content to campaigns to improve customer engagement!" }) })
|
|
362
|
-
] });
|
|
363
|
-
};
|
|
364
|
-
adminSdk.defineWidgetConfig({
|
|
365
|
-
zone: "campaign.list.before"
|
|
366
|
-
});
|
|
367
|
-
const useFlashSales = (pagination) => {
|
|
368
|
-
const query = reactQuery.useQuery({
|
|
369
|
-
queryKey: ["flash-sales", pagination],
|
|
370
|
-
queryFn: async () => {
|
|
371
|
-
const { data } = await axios__default.default.get("/admin/flash-sales", {
|
|
372
|
-
params: pagination
|
|
373
|
-
});
|
|
374
|
-
return data;
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
return query;
|
|
224
|
+
React.useEffect(() => {
|
|
225
|
+
fetchFlashSales();
|
|
226
|
+
}, [pagination.limit, pagination.offset]);
|
|
227
|
+
return {
|
|
228
|
+
data,
|
|
229
|
+
isLoading,
|
|
230
|
+
error,
|
|
231
|
+
refetch: fetchFlashSales
|
|
232
|
+
};
|
|
378
233
|
};
|
|
379
234
|
const FlashSalePage = () => {
|
|
380
235
|
const navigate = reactRouterDom.useNavigate();
|
|
@@ -452,10 +307,117 @@ const FlashSalePage = () => {
|
|
|
452
307
|
const FlashSale = () => {
|
|
453
308
|
return /* @__PURE__ */ jsxRuntime.jsx(FlashSalePage, {});
|
|
454
309
|
};
|
|
455
|
-
const config$
|
|
310
|
+
const config$3 = adminSdk.defineRouteConfig({
|
|
456
311
|
label: "Flash Sale",
|
|
457
312
|
icon: icons.Sparkles
|
|
458
313
|
});
|
|
314
|
+
const useCoupons = ({ limit, offset }) => {
|
|
315
|
+
const [data, setData] = React.useState(null);
|
|
316
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
317
|
+
const [error, setError] = React.useState(null);
|
|
318
|
+
const fetchCoupons = async () => {
|
|
319
|
+
setIsLoading(true);
|
|
320
|
+
setError(null);
|
|
321
|
+
try {
|
|
322
|
+
const response = await axios__default.default.get("/admin/coupons", {
|
|
323
|
+
params: { limit, offset }
|
|
324
|
+
});
|
|
325
|
+
setData(response.data);
|
|
326
|
+
} catch (err) {
|
|
327
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch coupons"));
|
|
328
|
+
} finally {
|
|
329
|
+
setIsLoading(false);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
React.useEffect(() => {
|
|
333
|
+
fetchCoupons();
|
|
334
|
+
}, [limit, offset]);
|
|
335
|
+
return {
|
|
336
|
+
data,
|
|
337
|
+
isLoading,
|
|
338
|
+
error,
|
|
339
|
+
refetch: fetchCoupons
|
|
340
|
+
};
|
|
341
|
+
};
|
|
342
|
+
const columnHelper$1 = ui.createDataTableColumnHelper();
|
|
343
|
+
const useCouponColumns = () => React.useMemo(
|
|
344
|
+
() => [
|
|
345
|
+
columnHelper$1.accessor("name", {
|
|
346
|
+
header: "Name",
|
|
347
|
+
cell: (info) => info.getValue()
|
|
348
|
+
}),
|
|
349
|
+
columnHelper$1.display({
|
|
350
|
+
id: "code",
|
|
351
|
+
header: "Code",
|
|
352
|
+
cell: (info) => {
|
|
353
|
+
var _a, _b;
|
|
354
|
+
return ((_b = (_a = info.row.original.promotions) == null ? void 0 : _a[0]) == null ? void 0 : _b.code) ?? "-";
|
|
355
|
+
}
|
|
356
|
+
}),
|
|
357
|
+
columnHelper$1.display({
|
|
358
|
+
id: "status",
|
|
359
|
+
header: "Status",
|
|
360
|
+
cell: (info) => {
|
|
361
|
+
const endsAt = info.row.original.ends_at;
|
|
362
|
+
if (!endsAt) {
|
|
363
|
+
return "";
|
|
364
|
+
}
|
|
365
|
+
const isActive = dayjs__default.default(endsAt).isAfter(dayjs__default.default());
|
|
366
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: isActive ? "green" : "grey", children: isActive ? "Active" : "Inactive" });
|
|
367
|
+
}
|
|
368
|
+
}),
|
|
369
|
+
columnHelper$1.accessor("starts_at", {
|
|
370
|
+
header: "Start Date",
|
|
371
|
+
cell: (info) => info.getValue() ? dayjs__default.default(info.getValue()).format("YYYY-MM-DD HH:mm") : ""
|
|
372
|
+
}),
|
|
373
|
+
columnHelper$1.accessor("ends_at", {
|
|
374
|
+
header: "End Date",
|
|
375
|
+
cell: (info) => info.getValue() ? dayjs__default.default(info.getValue()).format("YYYY-MM-DD HH:mm") : ""
|
|
376
|
+
})
|
|
377
|
+
],
|
|
378
|
+
[]
|
|
379
|
+
);
|
|
380
|
+
const CouponPage = () => {
|
|
381
|
+
const navigate = reactRouterDom.useNavigate();
|
|
382
|
+
const limit = 20;
|
|
383
|
+
const [pagination, setPagination] = React.useState({
|
|
384
|
+
pageSize: limit,
|
|
385
|
+
pageIndex: 0
|
|
386
|
+
});
|
|
387
|
+
const offset = pagination.pageIndex * limit;
|
|
388
|
+
const { data } = useCoupons({ limit, offset });
|
|
389
|
+
const columns2 = useCouponColumns();
|
|
390
|
+
const table = ui.useDataTable({
|
|
391
|
+
data: (data == null ? void 0 : data.campaigns) ?? [],
|
|
392
|
+
columns: columns2,
|
|
393
|
+
getRowId: (row) => row.id,
|
|
394
|
+
pagination: {
|
|
395
|
+
state: pagination,
|
|
396
|
+
onPaginationChange: setPagination
|
|
397
|
+
},
|
|
398
|
+
rowCount: (data == null ? void 0 : data.count) ?? 0,
|
|
399
|
+
onRowClick: (_, row) => {
|
|
400
|
+
navigate(`/coupons/${row.id}`);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
404
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
405
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-semibold", children: "Coupons" }),
|
|
406
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => navigate("/coupons/create"), children: "Create Coupon" })
|
|
407
|
+
] }),
|
|
408
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable, { instance: table, children: [
|
|
409
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {}),
|
|
410
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Pagination, {})
|
|
411
|
+
] })
|
|
412
|
+
] });
|
|
413
|
+
};
|
|
414
|
+
const Coupons = () => {
|
|
415
|
+
return /* @__PURE__ */ jsxRuntime.jsx(CouponPage, {});
|
|
416
|
+
};
|
|
417
|
+
const config$2 = adminSdk.defineRouteConfig({
|
|
418
|
+
label: "Coupons",
|
|
419
|
+
icon: icons.Tag
|
|
420
|
+
});
|
|
459
421
|
var isCheckBoxInput = (element) => element.type === "checkbox";
|
|
460
422
|
var isDateObject = (value) => value instanceof Date;
|
|
461
423
|
var isNullOrUndefined = (value) => value == null;
|
|
@@ -2363,13 +2325,25 @@ const ProductSelector = ({
|
|
|
2363
2325
|
const debouncedSearchHandler = lodash.debounce((value) => {
|
|
2364
2326
|
setSearch(value);
|
|
2365
2327
|
}, 500);
|
|
2366
|
-
const
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2328
|
+
const [products, setProducts] = React.useState(null);
|
|
2329
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
2330
|
+
React.useEffect(() => {
|
|
2331
|
+
const fetchProducts = async () => {
|
|
2332
|
+
setIsLoading(true);
|
|
2333
|
+
try {
|
|
2334
|
+
const result = await sdk.admin.product.list({
|
|
2335
|
+
q: search,
|
|
2336
|
+
limit: 100
|
|
2337
|
+
});
|
|
2338
|
+
setProducts(result);
|
|
2339
|
+
} catch (error) {
|
|
2340
|
+
console.error("Failed to fetch products:", error);
|
|
2341
|
+
} finally {
|
|
2342
|
+
setIsLoading(false);
|
|
2343
|
+
}
|
|
2344
|
+
};
|
|
2345
|
+
fetchProducts();
|
|
2346
|
+
}, [selectedProductIds, search]);
|
|
2373
2347
|
const selectableProducts = React.useMemo(() => {
|
|
2374
2348
|
var _a;
|
|
2375
2349
|
return (_a = products == null ? void 0 : products.products) == null ? void 0 : _a.filter(
|
|
@@ -2686,58 +2660,387 @@ const FlashSaleForm = ({
|
|
|
2686
2660
|
] }) })
|
|
2687
2661
|
] });
|
|
2688
2662
|
};
|
|
2689
|
-
const
|
|
2690
|
-
const
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2663
|
+
const FlashSaleCreate = () => {
|
|
2664
|
+
const navigate = reactRouterDom.useNavigate();
|
|
2665
|
+
async function handleSubmit(campaignData) {
|
|
2666
|
+
var _a;
|
|
2667
|
+
try {
|
|
2668
|
+
await axios__default.default.post("/admin/flash-sales", {
|
|
2669
|
+
...campaignData,
|
|
2670
|
+
starts_at: new Date(campaignData.starts_at).toUTCString(),
|
|
2671
|
+
ends_at: new Date(campaignData.ends_at).toUTCString()
|
|
2672
|
+
});
|
|
2673
|
+
ui.toast.success("Flash sale created successfully");
|
|
2674
|
+
navigate("/flash-sales");
|
|
2675
|
+
} catch (error) {
|
|
2676
|
+
let message;
|
|
2677
|
+
if (axios__default.default.isAxiosError(error)) {
|
|
2678
|
+
console.log(error);
|
|
2679
|
+
message = (_a = error.response) == null ? void 0 : _a.data.message;
|
|
2680
|
+
} else if (error instanceof Error) {
|
|
2681
|
+
message = error.message;
|
|
2682
|
+
} else {
|
|
2683
|
+
message = "Failed to create flash sale";
|
|
2684
|
+
}
|
|
2685
|
+
ui.toast.error("Failed to create flash sale", {
|
|
2686
|
+
description: message
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2691
|
+
FlashSaleForm,
|
|
2692
|
+
{
|
|
2693
|
+
onSubmit: handleSubmit,
|
|
2694
|
+
onCancel: () => navigate("/flash-sales")
|
|
2695
|
+
}
|
|
2696
|
+
);
|
|
2699
2697
|
};
|
|
2700
|
-
const
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2698
|
+
const couponSchema = z__default.default.object({
|
|
2699
|
+
name: z__default.default.string().min(1, "Name is required"),
|
|
2700
|
+
description: z__default.default.string().min(1, "Description is required"),
|
|
2701
|
+
type: z__default.default.literal("coupon"),
|
|
2702
|
+
code: z__default.default.string().min(1, "Coupon code is required"),
|
|
2703
|
+
discount_type: z__default.default.enum(["percentage", "fixed"]),
|
|
2704
|
+
discount_value: z__default.default.number().positive("Discount must be positive"),
|
|
2705
|
+
currency_code: z__default.default.string().optional(),
|
|
2706
|
+
starts_at: z__default.default.string().min(1, "Start date is required"),
|
|
2707
|
+
ends_at: z__default.default.string().min(1, "End date is required"),
|
|
2708
|
+
allocation: z__default.default.enum(["each", "total"]).optional(),
|
|
2709
|
+
target_type: z__default.default.enum(["order", "items"]).optional()
|
|
2710
|
+
}).superRefine((data, ctx) => {
|
|
2711
|
+
if (new Date(data.ends_at) < new Date(data.starts_at)) {
|
|
2712
|
+
ctx.addIssue({
|
|
2713
|
+
code: z__default.default.ZodIssueCode.custom,
|
|
2714
|
+
path: ["ends_at"],
|
|
2715
|
+
message: "End date must be after start date"
|
|
2716
|
+
});
|
|
2717
|
+
}
|
|
2718
|
+
if (data.discount_type === "fixed" && !data.currency_code) {
|
|
2719
|
+
ctx.addIssue({
|
|
2720
|
+
code: z__default.default.ZodIssueCode.custom,
|
|
2721
|
+
path: ["currency_code"],
|
|
2722
|
+
message: "Currency is required for fixed discount"
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
});
|
|
2726
|
+
const CouponForm = ({
|
|
2727
|
+
initialData,
|
|
2728
|
+
onSubmit,
|
|
2729
|
+
onCancel,
|
|
2730
|
+
disabled = false
|
|
2708
2731
|
}) => {
|
|
2709
|
-
const
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
)
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2732
|
+
const {
|
|
2733
|
+
register,
|
|
2734
|
+
control,
|
|
2735
|
+
handleSubmit,
|
|
2736
|
+
watch,
|
|
2737
|
+
setValue,
|
|
2738
|
+
formState: { errors }
|
|
2739
|
+
} = useForm({
|
|
2740
|
+
resolver: t(couponSchema),
|
|
2741
|
+
defaultValues: {
|
|
2742
|
+
name: (initialData == null ? void 0 : initialData.name) ?? "",
|
|
2743
|
+
description: (initialData == null ? void 0 : initialData.description) ?? "",
|
|
2744
|
+
type: "coupon",
|
|
2745
|
+
code: (initialData == null ? void 0 : initialData.code) ?? "",
|
|
2746
|
+
discount_type: (initialData == null ? void 0 : initialData.discount_type) ?? "percentage",
|
|
2747
|
+
discount_value: (initialData == null ? void 0 : initialData.discount_value) ?? 0,
|
|
2748
|
+
currency_code: (initialData == null ? void 0 : initialData.currency_code) ?? "",
|
|
2749
|
+
starts_at: (initialData == null ? void 0 : initialData.starts_at) ?? dayjs__default.default().startOf("day").format("YYYY-MM-DDTHH:mm"),
|
|
2750
|
+
ends_at: (initialData == null ? void 0 : initialData.ends_at) ?? dayjs__default.default().endOf("day").format("YYYY-MM-DDTHH:mm"),
|
|
2751
|
+
allocation: (initialData == null ? void 0 : initialData.allocation) ?? "total",
|
|
2752
|
+
target_type: (initialData == null ? void 0 : initialData.target_type) ?? "order"
|
|
2753
|
+
}
|
|
2754
|
+
});
|
|
2755
|
+
const discountType = watch("discount_type");
|
|
2756
|
+
const startsAt = watch("starts_at");
|
|
2757
|
+
const endsAt = watch("ends_at");
|
|
2758
|
+
const handleDateTimeChange = (field, type, value) => {
|
|
2759
|
+
const currentValue = watch(field);
|
|
2760
|
+
if (type === "date") {
|
|
2761
|
+
const time = currentValue ? dayjs__default.default(currentValue).format("HH:mm") : field === "starts_at" ? "00:00" : "23:59";
|
|
2762
|
+
setValue(field, `${value}T${time}`);
|
|
2763
|
+
} else {
|
|
2764
|
+
const date = currentValue ? dayjs__default.default(currentValue).format("YYYY-MM-DD") : dayjs__default.default().format("YYYY-MM-DD");
|
|
2765
|
+
setValue(field, `${date}T${value}`);
|
|
2766
|
+
}
|
|
2767
|
+
};
|
|
2768
|
+
const onFormSubmit = (data) => {
|
|
2769
|
+
onSubmit(data);
|
|
2770
|
+
};
|
|
2771
|
+
const onFormError = () => {
|
|
2772
|
+
const errorMessages = Object.values(errors).map((err) => err == null ? void 0 : err.message).filter(Boolean).join(", ");
|
|
2773
|
+
ui.toast.error("Invalid coupon data", {
|
|
2774
|
+
description: errorMessages
|
|
2775
|
+
});
|
|
2776
|
+
};
|
|
2777
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
2778
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2779
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-semibold", children: "Coupon" }),
|
|
2780
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "transparent", onClick: onCancel, children: "Cancel" })
|
|
2781
|
+
] }),
|
|
2782
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2783
|
+
"form",
|
|
2784
|
+
{
|
|
2785
|
+
onSubmit: handleSubmit(onFormSubmit, onFormError),
|
|
2786
|
+
className: "space-y-6 my-8",
|
|
2787
|
+
children: [
|
|
2788
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2789
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Name" }),
|
|
2790
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...register("name"), disabled }),
|
|
2791
|
+
errors.name && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.name.message })
|
|
2792
|
+
] }),
|
|
2793
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2794
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Description" }),
|
|
2795
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...register("description"), disabled }),
|
|
2796
|
+
errors.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
|
|
2797
|
+
] }),
|
|
2798
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2799
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2800
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Start Date" }),
|
|
2801
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
2802
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2803
|
+
ui.Input,
|
|
2804
|
+
{
|
|
2805
|
+
type: "date",
|
|
2806
|
+
value: startsAt ? dayjs__default.default(startsAt).format("YYYY-MM-DD") : "",
|
|
2807
|
+
onChange: (e2) => handleDateTimeChange("starts_at", "date", e2.target.value),
|
|
2808
|
+
disabled,
|
|
2809
|
+
className: "flex-1"
|
|
2810
|
+
}
|
|
2811
|
+
),
|
|
2812
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2813
|
+
ui.Input,
|
|
2814
|
+
{
|
|
2815
|
+
type: "time",
|
|
2816
|
+
value: startsAt ? dayjs__default.default(startsAt).format("HH:mm") : "",
|
|
2817
|
+
onChange: (e2) => handleDateTimeChange("starts_at", "time", e2.target.value),
|
|
2818
|
+
disabled,
|
|
2819
|
+
className: "flex-1"
|
|
2820
|
+
}
|
|
2821
|
+
)
|
|
2822
|
+
] }),
|
|
2823
|
+
errors.starts_at && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
|
|
2824
|
+
] }),
|
|
2825
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2826
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "End Date" }),
|
|
2827
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
2828
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2829
|
+
ui.Input,
|
|
2830
|
+
{
|
|
2831
|
+
type: "date",
|
|
2832
|
+
value: endsAt ? dayjs__default.default(endsAt).format("YYYY-MM-DD") : "",
|
|
2833
|
+
onChange: (e2) => handleDateTimeChange("ends_at", "date", e2.target.value),
|
|
2834
|
+
disabled,
|
|
2835
|
+
className: "flex-1"
|
|
2836
|
+
}
|
|
2837
|
+
),
|
|
2838
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2839
|
+
ui.Input,
|
|
2840
|
+
{
|
|
2841
|
+
type: "time",
|
|
2842
|
+
value: endsAt ? dayjs__default.default(endsAt).format("HH:mm") : "",
|
|
2843
|
+
onChange: (e2) => handleDateTimeChange("ends_at", "time", e2.target.value),
|
|
2844
|
+
disabled,
|
|
2845
|
+
className: "flex-1"
|
|
2846
|
+
}
|
|
2847
|
+
)
|
|
2848
|
+
] }),
|
|
2849
|
+
errors.ends_at && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
|
|
2850
|
+
] })
|
|
2851
|
+
] }),
|
|
2852
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2853
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2854
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Coupon Code" }),
|
|
2855
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...register("code"), disabled }),
|
|
2856
|
+
errors.code && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.code.message })
|
|
2857
|
+
] }),
|
|
2858
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2859
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Discount Type" }),
|
|
2860
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2861
|
+
Controller,
|
|
2862
|
+
{
|
|
2863
|
+
name: "discount_type",
|
|
2864
|
+
control,
|
|
2865
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2866
|
+
ui.Select,
|
|
2867
|
+
{
|
|
2868
|
+
value: field.value,
|
|
2869
|
+
onValueChange: field.onChange,
|
|
2870
|
+
disabled,
|
|
2871
|
+
children: [
|
|
2872
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select discount type" }) }),
|
|
2873
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
2874
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "percentage", children: "Percentage" }),
|
|
2875
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "fixed", children: "Fixed Amount" })
|
|
2876
|
+
] })
|
|
2877
|
+
]
|
|
2878
|
+
}
|
|
2879
|
+
)
|
|
2880
|
+
}
|
|
2881
|
+
)
|
|
2882
|
+
] })
|
|
2883
|
+
] }),
|
|
2884
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2885
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2886
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Discount Value" }),
|
|
2887
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2888
|
+
Controller,
|
|
2889
|
+
{
|
|
2890
|
+
name: "discount_value",
|
|
2891
|
+
control,
|
|
2892
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2893
|
+
ui.Input,
|
|
2894
|
+
{
|
|
2895
|
+
type: "number",
|
|
2896
|
+
value: field.value,
|
|
2897
|
+
min: 0,
|
|
2898
|
+
step: field.value % 1 === 0 ? 1 : 0.01,
|
|
2899
|
+
onChange: (e2) => field.onChange(Number(e2.target.value)),
|
|
2900
|
+
disabled
|
|
2901
|
+
}
|
|
2902
|
+
)
|
|
2903
|
+
}
|
|
2904
|
+
),
|
|
2905
|
+
errors.discount_value && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.discount_value.message })
|
|
2906
|
+
] }),
|
|
2907
|
+
discountType === "fixed" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2908
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Currency Code" }),
|
|
2909
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...register("currency_code"), disabled }),
|
|
2910
|
+
errors.currency_code && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.currency_code.message })
|
|
2911
|
+
] })
|
|
2912
|
+
] }),
|
|
2913
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2914
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2915
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Allocation" }),
|
|
2916
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2917
|
+
Controller,
|
|
2918
|
+
{
|
|
2919
|
+
name: "allocation",
|
|
2920
|
+
control,
|
|
2921
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2922
|
+
ui.Select,
|
|
2923
|
+
{
|
|
2924
|
+
value: field.value,
|
|
2925
|
+
onValueChange: field.onChange,
|
|
2926
|
+
disabled,
|
|
2927
|
+
children: [
|
|
2928
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select allocation" }) }),
|
|
2929
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
2930
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "total", children: "Order Total" }),
|
|
2931
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "each", children: "Each Item" })
|
|
2932
|
+
] })
|
|
2933
|
+
]
|
|
2934
|
+
}
|
|
2935
|
+
)
|
|
2936
|
+
}
|
|
2937
|
+
)
|
|
2938
|
+
] }),
|
|
2939
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2940
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Target Type" }),
|
|
2941
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2942
|
+
Controller,
|
|
2943
|
+
{
|
|
2944
|
+
name: "target_type",
|
|
2945
|
+
control,
|
|
2946
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2947
|
+
ui.Select,
|
|
2948
|
+
{
|
|
2949
|
+
value: field.value,
|
|
2950
|
+
onValueChange: field.onChange,
|
|
2951
|
+
disabled,
|
|
2952
|
+
children: [
|
|
2953
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select target" }) }),
|
|
2954
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
2955
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "order", children: "Order" }),
|
|
2956
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "items", children: "Items" })
|
|
2957
|
+
] })
|
|
2958
|
+
]
|
|
2959
|
+
}
|
|
2960
|
+
)
|
|
2961
|
+
}
|
|
2962
|
+
)
|
|
2963
|
+
] })
|
|
2964
|
+
] }),
|
|
2965
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
2966
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", type: "button", onClick: onCancel, children: "Cancel" }),
|
|
2967
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled, children: "Save Coupon" })
|
|
2968
|
+
] })
|
|
2969
|
+
]
|
|
2970
|
+
}
|
|
2971
|
+
)
|
|
2972
|
+
] });
|
|
2973
|
+
};
|
|
2974
|
+
const useCouponById = (id) => {
|
|
2975
|
+
const [data, setData] = React.useState(null);
|
|
2976
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
2977
|
+
const [error, setError] = React.useState(null);
|
|
2978
|
+
const fetchCoupon = async () => {
|
|
2979
|
+
if (!id) {
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
setIsLoading(true);
|
|
2983
|
+
setError(null);
|
|
2984
|
+
try {
|
|
2985
|
+
const response = await axios__default.default.get(`/admin/coupons/${id}`);
|
|
2986
|
+
setData(response.data);
|
|
2987
|
+
} catch (err) {
|
|
2988
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch coupon"));
|
|
2989
|
+
} finally {
|
|
2990
|
+
setIsLoading(false);
|
|
2991
|
+
}
|
|
2992
|
+
};
|
|
2993
|
+
React.useEffect(() => {
|
|
2994
|
+
fetchCoupon();
|
|
2995
|
+
}, [id]);
|
|
2996
|
+
return {
|
|
2997
|
+
data,
|
|
2998
|
+
isLoading,
|
|
2999
|
+
error,
|
|
3000
|
+
refetch: fetchCoupon
|
|
3001
|
+
};
|
|
3002
|
+
};
|
|
3003
|
+
const MarkdownEditor = ({
|
|
3004
|
+
label,
|
|
3005
|
+
value,
|
|
3006
|
+
onChange,
|
|
3007
|
+
placeholder,
|
|
3008
|
+
helpText,
|
|
3009
|
+
rows = 10,
|
|
3010
|
+
showPreview = true
|
|
3011
|
+
}) => {
|
|
3012
|
+
const [activeTab, setActiveTab] = React.useState("write");
|
|
3013
|
+
const insertMarkdown = (before, after = "") => {
|
|
3014
|
+
const textarea = document.getElementById(
|
|
3015
|
+
`markdown-${label}`
|
|
3016
|
+
);
|
|
3017
|
+
if (!textarea) return;
|
|
3018
|
+
const start = textarea.selectionStart;
|
|
3019
|
+
const end = textarea.selectionEnd;
|
|
3020
|
+
const selectedText = value.substring(start, end);
|
|
3021
|
+
const newText = value.substring(0, start) + before + selectedText + after + value.substring(end);
|
|
3022
|
+
onChange(newText);
|
|
3023
|
+
setTimeout(() => {
|
|
3024
|
+
textarea.focus();
|
|
3025
|
+
textarea.setSelectionRange(
|
|
3026
|
+
start + before.length,
|
|
3027
|
+
start + before.length + selectedText.length
|
|
3028
|
+
);
|
|
3029
|
+
}, 0);
|
|
3030
|
+
};
|
|
3031
|
+
const renderMarkdownPreview = (markdown) => {
|
|
3032
|
+
let html = markdown;
|
|
3033
|
+
html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
|
|
3034
|
+
html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
|
|
3035
|
+
html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
|
|
3036
|
+
html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
3037
|
+
html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
|
|
3038
|
+
html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
|
|
3039
|
+
html = html.replace(/_(.+?)_/g, "<em>$1</em>");
|
|
3040
|
+
html = html.replace(
|
|
3041
|
+
/```(\w+)?\n([\s\S]+?)```/g,
|
|
3042
|
+
'<pre class="bg-ui-bg-subtle p-4 rounded-lg overflow-x-auto"><code class="language-$1">$2</code></pre>'
|
|
3043
|
+
);
|
|
2741
3044
|
html = html.replace(
|
|
2742
3045
|
/`([^`]+)`/g,
|
|
2743
3046
|
'<code class="bg-ui-bg-subtle px-1 py-0.5 rounded text-sm">$1</code>'
|
|
@@ -3539,11 +3842,168 @@ const CampaignDetailForm = ({
|
|
|
3539
3842
|
)
|
|
3540
3843
|
] });
|
|
3541
3844
|
};
|
|
3845
|
+
const CouponDetail = () => {
|
|
3846
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3847
|
+
const { id } = reactRouterDom.useParams();
|
|
3848
|
+
const navigate = reactRouterDom.useNavigate();
|
|
3849
|
+
const [isEditing, setIsEditing] = React.useState(false);
|
|
3850
|
+
const { data, isLoading, refetch } = useCouponById(id ?? "");
|
|
3851
|
+
if (isLoading) {
|
|
3852
|
+
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" }) });
|
|
3853
|
+
}
|
|
3854
|
+
if (!(data == null ? void 0 : data.campaign)) {
|
|
3855
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Coupon not found" }) });
|
|
3856
|
+
}
|
|
3857
|
+
const campaign = data.campaign;
|
|
3858
|
+
const promotion = (_a = campaign.promotions) == null ? void 0 : _a[0];
|
|
3859
|
+
const initialValues = {
|
|
3860
|
+
name: campaign.name ?? "",
|
|
3861
|
+
description: campaign.description ?? "",
|
|
3862
|
+
type: "coupon",
|
|
3863
|
+
code: (promotion == null ? void 0 : promotion.code) ?? "",
|
|
3864
|
+
discount_type: ((_b = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _b.type) ?? "percentage",
|
|
3865
|
+
discount_value: Number(((_c = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _c.value) ?? 0),
|
|
3866
|
+
currency_code: ((_d = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _d.currency_code) ?? void 0,
|
|
3867
|
+
starts_at: campaign.starts_at ? dayjs__default.default(campaign.starts_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
3868
|
+
ends_at: campaign.ends_at ? dayjs__default.default(campaign.ends_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
3869
|
+
allocation: ((_e = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _e.allocation) ?? "total",
|
|
3870
|
+
target_type: ((_f = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _f.target_type) ?? "order"
|
|
3871
|
+
};
|
|
3872
|
+
const handleSubmit = async (formData) => {
|
|
3873
|
+
var _a2, _b2;
|
|
3874
|
+
if (!id) {
|
|
3875
|
+
return;
|
|
3876
|
+
}
|
|
3877
|
+
try {
|
|
3878
|
+
await axios__default.default.put(`/admin/coupons/${id}`, {
|
|
3879
|
+
...formData,
|
|
3880
|
+
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
3881
|
+
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
3882
|
+
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
3883
|
+
});
|
|
3884
|
+
ui.toast.success("Coupon updated successfully");
|
|
3885
|
+
setIsEditing(false);
|
|
3886
|
+
refetch();
|
|
3887
|
+
} catch (error) {
|
|
3888
|
+
let message = "Failed to update coupon";
|
|
3889
|
+
if (axios__default.default.isAxiosError(error)) {
|
|
3890
|
+
message = ((_b2 = (_a2 = error.response) == null ? void 0 : _a2.data) == null ? void 0 : _b2.message) ?? message;
|
|
3891
|
+
} else if (error instanceof Error) {
|
|
3892
|
+
message = error.message;
|
|
3893
|
+
}
|
|
3894
|
+
ui.toast.error("Failed to update coupon", {
|
|
3895
|
+
description: message
|
|
3896
|
+
});
|
|
3897
|
+
}
|
|
3898
|
+
};
|
|
3899
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
3900
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
3901
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3902
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold mb-1", children: campaign.name }),
|
|
3903
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle", children: "Manage coupon configuration and content" })
|
|
3904
|
+
] }),
|
|
3905
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
3906
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/coupons"), children: "Back to Coupons" }),
|
|
3907
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
|
|
3908
|
+
] })
|
|
3909
|
+
] }),
|
|
3910
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { defaultValue: "settings", children: [
|
|
3911
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
|
|
3912
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "settings", children: "Coupon Settings" }),
|
|
3913
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "details", children: "Coupon Details" })
|
|
3914
|
+
] }),
|
|
3915
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3916
|
+
CouponForm,
|
|
3917
|
+
{
|
|
3918
|
+
initialData: initialValues,
|
|
3919
|
+
onSubmit: handleSubmit,
|
|
3920
|
+
onCancel: () => navigate("/coupons"),
|
|
3921
|
+
disabled: !isEditing
|
|
3922
|
+
}
|
|
3923
|
+
) }),
|
|
3924
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3925
|
+
CampaignDetailForm,
|
|
3926
|
+
{
|
|
3927
|
+
campaignId: campaign.id,
|
|
3928
|
+
campaignName: campaign.name ?? ""
|
|
3929
|
+
}
|
|
3930
|
+
) })
|
|
3931
|
+
] })
|
|
3932
|
+
] });
|
|
3933
|
+
};
|
|
3934
|
+
const config$1 = adminSdk.defineRouteConfig({
|
|
3935
|
+
label: "Coupon Detail",
|
|
3936
|
+
icon: icons.Tag
|
|
3937
|
+
});
|
|
3938
|
+
const CouponCreate = () => {
|
|
3939
|
+
const navigate = reactRouterDom.useNavigate();
|
|
3940
|
+
const handleSubmit = async (formData) => {
|
|
3941
|
+
var _a, _b;
|
|
3942
|
+
try {
|
|
3943
|
+
await axios__default.default.post("/admin/coupons", {
|
|
3944
|
+
...formData,
|
|
3945
|
+
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
3946
|
+
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
3947
|
+
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
3948
|
+
});
|
|
3949
|
+
ui.toast.success("Coupon created successfully");
|
|
3950
|
+
navigate("/coupons");
|
|
3951
|
+
} catch (error) {
|
|
3952
|
+
let message = "Failed to create coupon";
|
|
3953
|
+
if (axios__default.default.isAxiosError(error)) {
|
|
3954
|
+
message = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) ?? message;
|
|
3955
|
+
} else if (error instanceof Error) {
|
|
3956
|
+
message = error.message;
|
|
3957
|
+
}
|
|
3958
|
+
ui.toast.error("Failed to create coupon", {
|
|
3959
|
+
description: message
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3962
|
+
};
|
|
3963
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3964
|
+
CouponForm,
|
|
3965
|
+
{
|
|
3966
|
+
onSubmit: handleSubmit,
|
|
3967
|
+
onCancel: () => navigate("/coupons")
|
|
3968
|
+
}
|
|
3969
|
+
);
|
|
3970
|
+
};
|
|
3971
|
+
const useFlashSaleById = (id) => {
|
|
3972
|
+
const [data, setData] = React.useState(null);
|
|
3973
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
3974
|
+
const [error, setError] = React.useState(null);
|
|
3975
|
+
const fetchFlashSale = async () => {
|
|
3976
|
+
if (!id) {
|
|
3977
|
+
setIsLoading(false);
|
|
3978
|
+
return;
|
|
3979
|
+
}
|
|
3980
|
+
setIsLoading(true);
|
|
3981
|
+
setError(null);
|
|
3982
|
+
try {
|
|
3983
|
+
const response = await axios__default.default.get(`/admin/flash-sales/${id}`);
|
|
3984
|
+
setData(response.data);
|
|
3985
|
+
} catch (err) {
|
|
3986
|
+
setError(
|
|
3987
|
+
err instanceof Error ? err : new Error("Failed to fetch flash sale")
|
|
3988
|
+
);
|
|
3989
|
+
} finally {
|
|
3990
|
+
setIsLoading(false);
|
|
3991
|
+
}
|
|
3992
|
+
};
|
|
3993
|
+
React.useEffect(() => {
|
|
3994
|
+
fetchFlashSale();
|
|
3995
|
+
}, [id]);
|
|
3996
|
+
return {
|
|
3997
|
+
data,
|
|
3998
|
+
isLoading,
|
|
3999
|
+
error,
|
|
4000
|
+
refetch: fetchFlashSale
|
|
4001
|
+
};
|
|
4002
|
+
};
|
|
3542
4003
|
const FlashSaleDetail = () => {
|
|
3543
4004
|
const { id } = reactRouterDom.useParams();
|
|
3544
4005
|
const navigate = reactRouterDom.useNavigate();
|
|
3545
|
-
const
|
|
3546
|
-
const { data, isLoading } = useFlashSaleById(id || "");
|
|
4006
|
+
const { data, isLoading, refetch } = useFlashSaleById(id || "");
|
|
3547
4007
|
const [isEditing, setIsEditing] = React.useState(false);
|
|
3548
4008
|
async function handleSubmit(campaignData2) {
|
|
3549
4009
|
var _a;
|
|
@@ -3554,14 +4014,7 @@ const FlashSaleDetail = () => {
|
|
|
3554
4014
|
ends_at: new Date(campaignData2.ends_at).toUTCString()
|
|
3555
4015
|
});
|
|
3556
4016
|
ui.toast.success("Flash sale updated successfully");
|
|
3557
|
-
|
|
3558
|
-
exact: false,
|
|
3559
|
-
predicate(query) {
|
|
3560
|
-
return ["flash-sales", "flash-sale"].includes(
|
|
3561
|
-
query.queryKey[0]
|
|
3562
|
-
);
|
|
3563
|
-
}
|
|
3564
|
-
});
|
|
4017
|
+
refetch();
|
|
3565
4018
|
navigate("/flash-sales");
|
|
3566
4019
|
} catch (error) {
|
|
3567
4020
|
let message;
|
|
@@ -3633,54 +4086,10 @@ const config = adminSdk.defineRouteConfig({
|
|
|
3633
4086
|
label: "Flash Sale Detail",
|
|
3634
4087
|
icon: icons.Sparkles
|
|
3635
4088
|
});
|
|
3636
|
-
const FlashSaleCreate = () => {
|
|
3637
|
-
const navigate = reactRouterDom.useNavigate();
|
|
3638
|
-
const queryClient = reactQuery.useQueryClient();
|
|
3639
|
-
async function handleSubmit(campaignData) {
|
|
3640
|
-
var _a;
|
|
3641
|
-
try {
|
|
3642
|
-
await axios__default.default.post("/admin/flash-sales", {
|
|
3643
|
-
...campaignData,
|
|
3644
|
-
starts_at: new Date(campaignData.starts_at).toUTCString(),
|
|
3645
|
-
ends_at: new Date(campaignData.ends_at).toUTCString()
|
|
3646
|
-
});
|
|
3647
|
-
ui.toast.success("Flash sale created successfully");
|
|
3648
|
-
queryClient.invalidateQueries({
|
|
3649
|
-
exact: false,
|
|
3650
|
-
queryKey: ["flash-sales"]
|
|
3651
|
-
});
|
|
3652
|
-
navigate("/flash-sales");
|
|
3653
|
-
} catch (error) {
|
|
3654
|
-
let message;
|
|
3655
|
-
if (axios__default.default.isAxiosError(error)) {
|
|
3656
|
-
console.log(error);
|
|
3657
|
-
message = (_a = error.response) == null ? void 0 : _a.data.message;
|
|
3658
|
-
} else if (error instanceof Error) {
|
|
3659
|
-
message = error.message;
|
|
3660
|
-
} else {
|
|
3661
|
-
message = "Failed to create flash sale";
|
|
3662
|
-
}
|
|
3663
|
-
ui.toast.error("Failed to create flash sale", {
|
|
3664
|
-
description: message
|
|
3665
|
-
});
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3669
|
-
FlashSaleForm,
|
|
3670
|
-
{
|
|
3671
|
-
onSubmit: handleSubmit,
|
|
3672
|
-
onCancel: () => navigate("/flash-sales")
|
|
3673
|
-
}
|
|
3674
|
-
);
|
|
3675
|
-
};
|
|
3676
4089
|
const widgetModule = { widgets: [
|
|
3677
4090
|
{
|
|
3678
4091
|
Component: CampaignDetailWidget,
|
|
3679
4092
|
zone: ["promotion.details.side.after"]
|
|
3680
|
-
},
|
|
3681
|
-
{
|
|
3682
|
-
Component: CampaignStatsWidget,
|
|
3683
|
-
zone: ["campaign.list.before"]
|
|
3684
4093
|
}
|
|
3685
4094
|
] };
|
|
3686
4095
|
const routeModule = {
|
|
@@ -3690,21 +4099,45 @@ const routeModule = {
|
|
|
3690
4099
|
path: "/flash-sales"
|
|
3691
4100
|
},
|
|
3692
4101
|
{
|
|
3693
|
-
Component:
|
|
3694
|
-
path: "/
|
|
4102
|
+
Component: Coupons,
|
|
4103
|
+
path: "/coupons"
|
|
3695
4104
|
},
|
|
3696
4105
|
{
|
|
3697
4106
|
Component: FlashSaleCreate,
|
|
3698
4107
|
path: "/flash-sales/create"
|
|
4108
|
+
},
|
|
4109
|
+
{
|
|
4110
|
+
Component: CouponDetail,
|
|
4111
|
+
path: "/coupons/:id"
|
|
4112
|
+
},
|
|
4113
|
+
{
|
|
4114
|
+
Component: CouponCreate,
|
|
4115
|
+
path: "/coupons/create"
|
|
4116
|
+
},
|
|
4117
|
+
{
|
|
4118
|
+
Component: FlashSaleDetail,
|
|
4119
|
+
path: "/flash-sales/:id"
|
|
3699
4120
|
}
|
|
3700
4121
|
]
|
|
3701
4122
|
};
|
|
3702
4123
|
const menuItemModule = {
|
|
3703
4124
|
menuItems: [
|
|
4125
|
+
{
|
|
4126
|
+
label: config$2.label,
|
|
4127
|
+
icon: config$2.icon,
|
|
4128
|
+
path: "/coupons",
|
|
4129
|
+
nested: void 0
|
|
4130
|
+
},
|
|
4131
|
+
{
|
|
4132
|
+
label: config$3.label,
|
|
4133
|
+
icon: config$3.icon,
|
|
4134
|
+
path: "/flash-sales",
|
|
4135
|
+
nested: void 0
|
|
4136
|
+
},
|
|
3704
4137
|
{
|
|
3705
4138
|
label: config$1.label,
|
|
3706
4139
|
icon: config$1.icon,
|
|
3707
|
-
path: "/
|
|
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;
|