@lodashventure/medusa-campaign 1.4.1 → 1.4.3

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