@lodashventure/medusa-campaign 1.4.0 → 1.4.2

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