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