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