@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
@@ -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, Input, Label, Textarea, Table, Select, FocusModal, toast, Tabs, clx } from "@medusajs/ui";
4
- import { Sparkles, PhotoSolid, PencilSquare, Eye, CheckCircleSolid, ClockSolid, Trash, InformationCircle, CommandLine, X, CloudArrowUp } from "@medusajs/icons";
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 = ({ data: promotion }) => {
12
+ const CampaignDetailWidget = ({
13
+ data: promotion
14
+ }) => {
14
15
  const navigate = useNavigate();
15
16
  const [campaign, setCampaign] = useState(null);
16
- const [campaignDetail, setCampaignDetail] = useState(null);
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(PhotoSolid, { className: `h-4 w-4 ${hasImages ? "text-green-500" : "text-ui-fg-muted"}` }),
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(PencilSquare, { className: `h-4 w-4 ${hasContent ? "text-green-500" : "text-ui-fg-muted"}` }),
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
- Button,
154
- {
155
- variant: "secondary",
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 CampaignStatsWidget = () => {
193
- const [stats, setStats] = useState({
194
- total: 0,
195
- active: 0,
196
- inactive: 0,
197
- with_images: 0,
198
- with_content: 0,
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 fetch("/admin/flash-sales?limit=100", {
209
- credentials: "include"
205
+ const response = await axios.get("/admin/coupons", {
206
+ params: { limit, offset }
210
207
  });
211
- if (response.ok) {
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
- console.error("Error fetching campaign stats:", err);
210
+ setError(err instanceof Error ? err : new Error("Failed to fetch coupons"));
249
211
  } finally {
250
- setLoading(false);
212
+ setIsLoading(false);
251
213
  }
252
214
  };
253
- if (loading) {
254
- return /* @__PURE__ */ jsxs(Container, { className: "px-6 py-6", children: [
255
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
256
- /* @__PURE__ */ jsx(Sparkles, { className: "text-ui-fg-subtle" }),
257
- /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Campaign Overview" })
258
- ] }),
259
- /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Loading stats..." })
260
- ] });
261
- }
262
- const completionRate = stats.total > 0 ? Math.round(stats.complete / stats.total * 100) : 0;
263
- return /* @__PURE__ */ jsxs(Container, { className: "px-6 py-6", children: [
264
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-6", children: [
265
- /* @__PURE__ */ jsx(Sparkles, { className: "text-ui-fg-subtle" }),
266
- /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Campaign Overview" })
267
- ] }),
268
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-4 mb-6", children: [
269
- /* @__PURE__ */ jsxs("div", { className: "p-4 rounded-lg border bg-ui-bg-subtle", children: [
270
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
271
- /* @__PURE__ */ jsx(Text, { className: "text-xs text-ui-fg-subtle", children: "Total" }),
272
- /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4 text-ui-fg-subtle" })
273
- ] }),
274
- /* @__PURE__ */ jsx(Text, { className: "text-2xl font-bold", children: stats.total })
275
- ] }),
276
- /* @__PURE__ */ jsxs("div", { className: "p-4 rounded-lg border bg-green-50", children: [
277
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
278
- /* @__PURE__ */ jsx(Text, { className: "text-xs text-green-700", children: "Active" }),
279
- /* @__PURE__ */ jsx(CheckCircleSolid, { className: "h-4 w-4 text-green-600" })
280
- ] }),
281
- /* @__PURE__ */ jsx(Text, { className: "text-2xl font-bold text-green-700", children: stats.active })
282
- ] }),
283
- /* @__PURE__ */ jsxs("div", { className: "p-4 rounded-lg border bg-gray-50", children: [
284
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
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
- /* @__PURE__ */ jsxs(Text, { className: "text-xs text-ui-fg-muted mt-2", children: [
348
- stats.complete,
349
- " of ",
350
- stats.total,
351
- " campaigns have complete details"
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
- completionRate < 50 && stats.total > 0 && /* @__PURE__ */ jsx("div", { className: "mt-4 p-3 rounded-lg bg-blue-50 border border-blue-200", children: /* @__PURE__ */ jsx(Text, { className: "text-xs text-blue-800", children: "💡 Tip: Add images and content to campaigns to improve customer engagement!" }) })
291
+ /* @__PURE__ */ jsxs(DataTable, { instance: table, children: [
292
+ /* @__PURE__ */ jsx(DataTable.Table, {}),
293
+ /* @__PURE__ */ jsx(DataTable.Pagination, {})
294
+ ] })
355
295
  ] });
356
296
  };
357
- defineWidgetConfig({
358
- zone: "campaign.list.before"
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 query = useQuery({
362
- queryKey: ["flash-sales", pagination],
363
- queryFn: async () => {
364
- const { data } = await axios.get("/admin/flash-sales", {
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
- return data;
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
- return query;
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$1 = defineRouteConfig({
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 sdk = new Medusa({
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("flash-sale"),
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
- const campaignProductSchema = z.object({
2400
- product: z.object({
2401
- id: z.string(),
2402
- title: z.string()
2403
- }),
2404
- discountType: z.enum([
2405
- "percentage"
2406
- // , "fixed"
2407
- ]),
2408
- discountValue: z.number().min(1),
2409
- limit: z.number().min(1),
2410
- maxQty: z.number().min(1)
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 campaignDataSchema = customCampaignSchema.extend({
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(campaignDataSchema),
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: "flash-sale",
2439
- starts_at: (initialData == null ? void 0 : initialData.starts_at) || "",
2440
- ends_at: (initialData == null ? void 0 : initialData.ends_at) || "",
2441
- products: initialProducts ? Array.from(initialProducts.values()) : []
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 { fields, append, remove } = useFieldArray({
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.entries(errors).map(([key, value]) => {
2466
- var _a2;
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: "Flash sale campaign" }),
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-4 my-8",
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: "w-32"
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: "w-32"
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: "flex justify-between items-center", children: [
2554
- /* @__PURE__ */ jsx(Label, { children: "Products" }),
2555
- /* @__PURE__ */ jsx(
2556
- Button,
2557
- {
2558
- type: "button",
2559
- variant: "secondary",
2560
- onClick: () => setOpenProductModal(true),
2561
- disabled,
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: `products.${index}.discountType`,
2460
+ name: "discount_type",
2582
2461
  control,
2583
- render: ({ field: field2 }) => /* @__PURE__ */ jsxs(
2462
+ render: ({ field }) => /* @__PURE__ */ jsxs(
2584
2463
  Select,
2585
2464
  {
2586
- value: field2.value,
2587
- onValueChange: field2.onChange,
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__ */ jsx(Select.Content, { children: /* @__PURE__ */ jsx(Select.Item, { value: "percentage", children: "Percentage" }) })
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
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
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: `products.${index}.discountValue`,
2487
+ name: "discount_value",
2601
2488
  control,
2602
- render: ({ field: field2 }) => /* @__PURE__ */ jsx(
2489
+ render: ({ field }) => /* @__PURE__ */ jsx(
2603
2490
  Input,
2604
2491
  {
2605
2492
  type: "number",
2606
- value: field2.value,
2607
- onChange: (e2) => field2.onChange(Number(e2.target.value)),
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(Table.Cell, { children: /* @__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: `products.${index}.limit`,
2516
+ name: "allocation",
2617
2517
  control,
2618
- render: ({ field: field2 }) => /* @__PURE__ */ jsx(
2619
- Input,
2518
+ render: ({ field }) => /* @__PURE__ */ jsxs(
2519
+ Select,
2620
2520
  {
2621
- type: "number",
2622
- value: field2.value,
2623
- onChange: (e2) => field2.onChange(Number(e2.target.value)),
2624
- disabled
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
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
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: `products.${index}.maxQty`,
2541
+ name: "target_type",
2633
2542
  control,
2634
- render: ({ field: field2 }) => /* @__PURE__ */ jsx(
2635
- Input,
2543
+ render: ({ field }) => /* @__PURE__ */ jsxs(
2544
+ Select,
2636
2545
  {
2637
- type: "number",
2638
- value: field2.value,
2639
- onChange: (e2) => field2.onChange(Number(e2.target.value)),
2640
- disabled
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
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
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__ */ jsx(Button, { type: "submit", disabled, children: "Save" })
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 useFlashSaleById = (id) => {
2683
- const query = useQuery({
2684
- queryKey: ["flash-sale", id],
2685
- queryFn: async () => {
2686
- const { data } = await axios.get(`/admin/flash-sales/${id}`);
2687
- return data;
2688
- },
2689
- enabled: !!id
2690
- });
2691
- return query;
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 FlashSaleDetail = () => {
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
- async function handleSubmit(campaignData2) {
3542
- var _a;
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/flash-sales/${id}`, {
3545
- ...campaignData2,
3546
- starts_at: new Date(campaignData2.starts_at).toUTCString(),
3547
- ends_at: new Date(campaignData2.ends_at).toUTCString()
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
- navigate("/flash-sales");
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
- console.log(error);
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 flash sale", {
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: data.name }),
3591
- /* @__PURE__ */ jsx("p", { className: "text-ui-fg-subtle", children: "Manage campaign settings and content" })
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("/flash-sales"), children: "Back to Campaigns" }),
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: "Campaign Settings" }),
3601
- /* @__PURE__ */ jsx(Tabs.Trigger, { value: "details", children: "Campaign Details" })
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
- FlashSaleForm,
3513
+ CouponForm,
3605
3514
  {
3606
- initialData: campaignData,
3607
- initialProducts: new Map(
3608
- data.products.map((product) => [product.product.id, product])
3609
- ),
3515
+ initialData: initialValues,
3610
3516
  onSubmit: handleSubmit,
3611
- onCancel: () => navigate("/flash-sales"),
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: data.name || ""
3524
+ campaignId: campaign.id,
3525
+ campaignName: campaign.name ?? ""
3620
3526
  }
3621
3527
  ) })
3622
3528
  ] })
3623
3529
  ] });
3624
3530
  };
3625
- const config = defineRouteConfig({
3626
- label: "Flash Sale Detail",
3627
- icon: Sparkles
3531
+ const config$1 = defineRouteConfig({
3532
+ label: "Coupon Detail",
3533
+ icon: Tag
3628
3534
  });
3629
- const FlashSaleCreate = () => {
3535
+ const CouponCreate = () => {
3630
3536
  const navigate = useNavigate();
3631
- const queryClient = useQueryClient();
3632
- async function handleSubmit(campaignData) {
3633
- var _a;
3537
+ const handleSubmit = async (formData) => {
3538
+ var _a, _b;
3634
3539
  try {
3635
- await axios.post("/admin/flash-sales", {
3636
- ...campaignData,
3637
- starts_at: new Date(campaignData.starts_at).toUTCString(),
3638
- ends_at: new Date(campaignData.ends_at).toUTCString()
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
- navigate("/flash-sales");
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
- console.log(error);
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 flash sale", {
3555
+ toast.error("Failed to create coupon", {
3657
3556
  description: message
3658
3557
  });
3659
3558
  }
3660
- }
3559
+ };
3661
3560
  return /* @__PURE__ */ jsx(
3662
- FlashSaleForm,
3561
+ CouponForm,
3663
3562
  {
3664
3563
  onSubmit: handleSubmit,
3665
- onCancel: () => navigate("/flash-sales")
3564
+ onCancel: () => navigate("/coupons")
3666
3565
  }
3667
3566
  );
3668
3567
  };
3669
- const widgetModule = { widgets: [
3670
- {
3671
- Component: CampaignDetailWidget,
3672
- zone: ["promotion.details.side.after"]
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 routeModule = {
3680
- routes: [
3681
- {
3682
- Component: FlashSale,
3683
- path: "/flash-sales"
3684
- },
3685
- {
3686
- Component: FlashSaleDetail,
3687
- path: "/flash-sales/:id"
3688
- },
3689
- {
3690
- Component: FlashSaleCreate,
3691
- path: "/flash-sales/create"
3692
- }
3693
- ]
3694
- };
3695
- const menuItemModule = {
3696
- menuItems: [
3697
- {
3698
- label: config$1.label,
3699
- icon: config$1.icon,
3700
- path: "/flash-sales",
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