@lodashventure/medusa-campaign 1.3.12 → 1.4.0

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 (54) hide show
  1. package/.medusa/server/src/admin/index.js +1238 -16
  2. package/.medusa/server/src/admin/index.mjs +1240 -18
  3. package/.medusa/server/src/api/admin/campaigns/[id]/detail/route.js +67 -0
  4. package/.medusa/server/src/api/admin/campaigns/[id]/image/route.js +80 -0
  5. package/.medusa/server/src/api/admin/campaigns/[id]/thumbnail/route.js +80 -0
  6. package/.medusa/server/src/api/admin/campaigns/sync/route.js +8 -6
  7. package/.medusa/server/src/api/admin/flash-sales/[id]/route.js +22 -4
  8. package/.medusa/server/src/api/middlewares.js +24 -1
  9. package/.medusa/server/src/api/store/buy-x-get-y/[id]/route.js +1 -1
  10. package/.medusa/server/src/api/store/buy-x-get-y/products/[productId]/route.js +1 -1
  11. package/.medusa/server/src/api/store/buy-x-get-y/route.js +3 -1
  12. package/.medusa/server/src/api/store/campaigns/[id]/route.js +40 -12
  13. package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251024000000.js +53 -0
  14. package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251025000000.js +22 -0
  15. package/.medusa/server/src/modules/custom-campaigns/models/campaign-detail.js +26 -0
  16. package/.medusa/server/src/modules/custom-campaigns/service.js +3 -1
  17. package/.medusa/server/src/subscribers/order-placed.js +2 -2
  18. package/.medusa/server/src/workflows/buy-x-get-y/applyBuyXGetYToCartWorkflow.js +16 -4
  19. package/.medusa/server/src/workflows/campaign-detail/update-campaign-detail.js +55 -0
  20. package/.medusa/server/src/workflows/campaign-detail/upload-campaign-images.js +120 -0
  21. package/.medusa/server/src/workflows/custom-campaign/createBuyXGetYCampaignWorkflow.js +13 -14
  22. package/package.json +7 -5
  23. package/src/admin/components/campaign-detail-form.tsx +407 -0
  24. package/src/admin/components/campaign-image-uploader.tsx +313 -0
  25. package/src/admin/components/markdown-editor.tsx +298 -0
  26. package/src/admin/routes/flash-sales/[id]/page.tsx +51 -14
  27. package/src/admin/widgets/campaign-detail-widget.tsx +299 -0
  28. package/src/admin/widgets/campaign-stats-widget.tsx +238 -0
  29. package/src/api/admin/campaigns/[id]/detail/route.ts +77 -0
  30. package/src/api/admin/campaigns/[id]/image/route.ts +87 -0
  31. package/src/api/admin/campaigns/[id]/thumbnail/route.ts +87 -0
  32. package/src/api/admin/campaigns/sync/route.ts +53 -28
  33. package/src/api/admin/flash-sales/[id]/route.ts +50 -19
  34. package/src/api/middlewares.ts +21 -0
  35. package/src/api/store/buy-x-get-y/[id]/route.ts +10 -10
  36. package/src/api/store/buy-x-get-y/products/[productId]/route.ts +11 -12
  37. package/src/api/store/buy-x-get-y/route.ts +12 -5
  38. package/src/api/store/campaigns/[id]/route.ts +54 -24
  39. package/src/modules/custom-campaigns/migrations/Migration20251024000000.ts +53 -0
  40. package/src/modules/custom-campaigns/migrations/Migration20251025000000.ts +19 -0
  41. package/src/modules/custom-campaigns/models/campaign-detail.ts +25 -0
  42. package/src/modules/custom-campaigns/service.ts +2 -0
  43. package/src/subscribers/order-placed.ts +0 -2
  44. package/src/types/index.d.ts +46 -0
  45. package/src/workflows/buy-x-get-y/applyBuyXGetYToCartWorkflow.ts +41 -18
  46. package/src/workflows/campaign-detail/update-campaign-detail.ts +85 -0
  47. package/src/workflows/campaign-detail/upload-campaign-images.ts +163 -0
  48. package/src/workflows/custom-campaign/createBuyXGetYCampaignWorkflow.ts +23 -22
  49. package/.medusa/server/src/api/admin/campaigns/fix-dates/route.js +0 -103
  50. package/.medusa/server/src/api/admin/force-fix/route.js +0 -176
  51. package/.medusa/server/src/api/admin/test-campaign/route.js +0 -132
  52. package/src/api/admin/campaigns/fix-dates/route.ts +0 -107
  53. package/src/api/admin/force-fix/route.ts +0 -184
  54. package/src/api/admin/test-campaign/route.ts +0 -141
@@ -1,22 +1,369 @@
1
1
  "use strict";
2
2
  const jsxRuntime = require("react/jsx-runtime");
3
3
  const adminSdk = require("@medusajs/admin-sdk");
4
- const icons = require("@medusajs/icons");
5
4
  const ui = require("@medusajs/ui");
6
- const dayjs = require("dayjs");
5
+ const icons = require("@medusajs/icons");
7
6
  const React = require("react");
8
7
  const reactRouterDom = require("react-router-dom");
8
+ const dayjs = require("dayjs");
9
9
  const reactQuery = require("@tanstack/react-query");
10
10
  const axios = require("axios");
11
11
  const z = require("zod");
12
12
  const lodash = require("lodash");
13
13
  const Medusa = require("@medusajs/js-sdk");
14
14
  const _interopDefault = (e2) => e2 && e2.__esModule ? e2 : { default: e2 };
15
- const dayjs__default = /* @__PURE__ */ _interopDefault(dayjs);
16
15
  const React__default = /* @__PURE__ */ _interopDefault(React);
16
+ const dayjs__default = /* @__PURE__ */ _interopDefault(dayjs);
17
17
  const axios__default = /* @__PURE__ */ _interopDefault(axios);
18
18
  const z__default = /* @__PURE__ */ _interopDefault(z);
19
19
  const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
20
+ const CampaignDetailWidget = ({ data: promotion }) => {
21
+ const navigate = reactRouterDom.useNavigate();
22
+ const [campaign, setCampaign] = React.useState(null);
23
+ const [campaignDetail, setCampaignDetail] = React.useState(null);
24
+ const [loading, setLoading] = React.useState(true);
25
+ const [error, setError] = React.useState(null);
26
+ React.useEffect(() => {
27
+ if (promotion == null ? void 0 : promotion.campaign_id) {
28
+ fetchCampaignAndDetail();
29
+ } else {
30
+ setLoading(false);
31
+ }
32
+ }, [promotion == null ? void 0 : promotion.campaign_id]);
33
+ const fetchCampaignAndDetail = async () => {
34
+ if (!(promotion == null ? void 0 : promotion.campaign_id)) return;
35
+ setLoading(true);
36
+ setError(null);
37
+ try {
38
+ const campaignResponse = await fetch(
39
+ `/admin/flash-sales/${promotion.campaign_id}`,
40
+ {
41
+ credentials: "include"
42
+ }
43
+ );
44
+ if (campaignResponse.ok) {
45
+ const campaignData = await campaignResponse.json();
46
+ setCampaign(campaignData);
47
+ if (campaignData.campaign_detail) {
48
+ setCampaignDetail(campaignData.campaign_detail);
49
+ }
50
+ }
51
+ } catch (err) {
52
+ console.error("Error fetching campaign:", err);
53
+ setError("Failed to load campaign information");
54
+ } finally {
55
+ setLoading(false);
56
+ }
57
+ };
58
+ const handleEditCampaign = () => {
59
+ if (campaign == null ? void 0 : campaign.id) {
60
+ navigate(`/flash-sales/${campaign.id}`);
61
+ }
62
+ };
63
+ const handleViewCampaign = () => {
64
+ if (campaign == null ? void 0 : campaign.id) {
65
+ navigate(`/flash-sales/${campaign.id}#details`);
66
+ }
67
+ };
68
+ if (!(promotion == null ? void 0 : promotion.campaign_id)) {
69
+ return null;
70
+ }
71
+ if (loading) {
72
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y px-0 pb-0 pt-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-6", children: [
73
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
74
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Sparkles, { className: "text-ui-fg-subtle" }),
75
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Campaign" })
76
+ ] }),
77
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Loading campaign..." })
78
+ ] }) });
79
+ }
80
+ if (error) {
81
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y px-0 pb-0 pt-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-6", children: [
82
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
83
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Sparkles, { className: "text-ui-fg-subtle" }),
84
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Campaign" })
85
+ ] }),
86
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "error", children: error })
87
+ ] }) });
88
+ }
89
+ if (!campaign) {
90
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y px-0 pb-0 pt-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-6", children: [
91
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
92
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Sparkles, { className: "text-ui-fg-subtle" }),
93
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Campaign" })
94
+ ] }),
95
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "This promotion is not part of a campaign" })
96
+ ] }) });
97
+ }
98
+ const hasCampaignDetail = !!campaignDetail;
99
+ const hasImages = (campaignDetail == null ? void 0 : campaignDetail.image_url) || (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url);
100
+ const hasContent = (campaignDetail == null ? void 0 : campaignDetail.detail_content) || (campaignDetail == null ? void 0 : campaignDetail.terms_and_conditions);
101
+ const hasSEO = (campaignDetail == null ? void 0 : campaignDetail.meta_title) || (campaignDetail == null ? void 0 : campaignDetail.meta_description);
102
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "divide-y px-0 pb-0 pt-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-6", children: [
103
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mb-4", children: [
104
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Sparkles, { className: "text-ui-fg-subtle" }),
105
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Campaign" })
106
+ ] }),
107
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4 p-4 rounded-lg border bg-ui-bg-subtle", children: [
108
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between mb-3", children: [
109
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
110
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-semibold text-lg mb-1", children: campaign.name }),
111
+ campaign.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle line-clamp-2", children: campaign.description })
112
+ ] }),
113
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { color: "purple", size: "small", children: [
114
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Sparkles, { className: "mr-1" }),
115
+ "Campaign"
116
+ ] })
117
+ ] }),
118
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2 mt-3 pt-3 border-t", children: [
119
+ /* @__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"}` }),
121
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs", children: hasImages ? "Images added" : "No images" })
122
+ ] }),
123
+ /* @__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"}` }),
125
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs", children: hasContent ? "Content added" : "No content" })
126
+ ] })
127
+ ] })
128
+ ] }),
129
+ hasCampaignDetail && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 mb-4", children: [
130
+ campaignDetail.thumbnail_url && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
131
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs font-medium text-ui-fg-subtle mb-2", children: "Thumbnail" }),
132
+ /* @__PURE__ */ jsxRuntime.jsx(
133
+ "img",
134
+ {
135
+ src: campaignDetail.thumbnail_url,
136
+ alt: "Campaign thumbnail",
137
+ className: "w-full h-32 object-cover rounded-lg border"
138
+ }
139
+ )
140
+ ] }),
141
+ campaignDetail.detail_content && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
142
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-xs font-medium text-ui-fg-subtle mb-1", children: "Content Preview" }),
143
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-sm line-clamp-3 text-ui-fg-muted", children: [
144
+ campaignDetail.detail_content.substring(0, 150),
145
+ "..."
146
+ ] })
147
+ ] }),
148
+ campaignDetail.link_url && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
149
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "xsmall", color: "blue", children: "CTA Link" }),
150
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle truncate", children: campaignDetail.link_text || campaignDetail.link_url })
151
+ ] }),
152
+ hasSEO && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
153
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "xsmall", color: "green", children: "SEO" }),
154
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Meta fields configured" })
155
+ ] })
156
+ ] }),
157
+ !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
+ /* @__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
+ ),
171
+ hasCampaignDetail && /* @__PURE__ */ jsxRuntime.jsxs(
172
+ ui.Button,
173
+ {
174
+ variant: "secondary",
175
+ size: "small",
176
+ onClick: handleViewCampaign,
177
+ children: [
178
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Eye, { className: "mr-1" }),
179
+ "View Details"
180
+ ]
181
+ }
182
+ )
183
+ ] }),
184
+ campaignDetail && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 pt-4 border-t grid grid-cols-2 gap-3 text-xs", children: [
185
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
186
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Display Order" }),
187
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: campaignDetail.display_order })
188
+ ] }),
189
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
190
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Status" }),
191
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: hasCampaignDetail ? "green" : "grey", size: "xsmall", children: hasCampaignDetail ? "Complete" : "Incomplete" })
192
+ ] })
193
+ ] })
194
+ ] }) });
195
+ };
196
+ adminSdk.defineWidgetConfig({
197
+ zone: "promotion.details.side.after"
198
+ });
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);
214
+ try {
215
+ const response = await fetch("/admin/flash-sales?limit=100", {
216
+ credentials: "include"
217
+ });
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
+ }
254
+ } catch (err) {
255
+ console.error("Error fetching campaign stats:", err);
256
+ } finally {
257
+ setLoading(false);
258
+ }
259
+ };
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
+ });
20
367
  const useFlashSales = (pagination) => {
21
368
  const query = reactQuery.useQuery({
22
369
  queryKey: ["flash-sales", pagination],
@@ -2350,6 +2697,848 @@ const useFlashSaleById = (id) => {
2350
2697
  });
2351
2698
  return query;
2352
2699
  };
2700
+ const MarkdownEditor = ({
2701
+ label,
2702
+ value,
2703
+ onChange,
2704
+ placeholder,
2705
+ helpText,
2706
+ rows = 10,
2707
+ showPreview = true
2708
+ }) => {
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
+ );
2741
+ html = html.replace(
2742
+ /`([^`]+)`/g,
2743
+ '<code class="bg-ui-bg-subtle px-1 py-0.5 rounded text-sm">$1</code>'
2744
+ );
2745
+ html = html.replace(
2746
+ /\[([^\]]+)\]\(([^)]+)\)/g,
2747
+ '<a href="$2" class="text-ui-fg-interactive underline">$1</a>'
2748
+ );
2749
+ html = html.replace(
2750
+ /!\[([^\]]*)\]\(([^)]+)\)/g,
2751
+ '<img src="$2" alt="$1" class="max-w-full h-auto rounded-lg my-2" />'
2752
+ );
2753
+ html = html.replace(/^\* (.+)$/gim, "<li>$1</li>");
2754
+ html = html.replace(/^\- (.+)$/gim, "<li>$1</li>");
2755
+ html = html.replace(
2756
+ /(<li>.*<\/li>)/s,
2757
+ "<ul class='list-disc ml-6'>$1</ul>"
2758
+ );
2759
+ html = html.replace(/\n\n/g, "</p><p>");
2760
+ html = `<p>${html}</p>`;
2761
+ return html;
2762
+ };
2763
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2764
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2765
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: `markdown-${label}`, className: "font-medium", children: label }),
2766
+ helpText && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-xs text-ui-fg-subtle", children: [
2767
+ /* @__PURE__ */ jsxRuntime.jsx(icons.InformationCircle, { className: "h-4 w-4" }),
2768
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: helpText })
2769
+ ] })
2770
+ ] }),
2771
+ showPreview ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { value: activeTab, onValueChange: (v) => setActiveTab(v), children: [
2772
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
2773
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "write", children: "Write" }),
2774
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "preview", children: "Preview" })
2775
+ ] }),
2776
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "write", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2777
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
2778
+ /* @__PURE__ */ jsxRuntime.jsx(
2779
+ ui.Button,
2780
+ {
2781
+ type: "button",
2782
+ variant: "secondary",
2783
+ size: "small",
2784
+ onClick: () => insertMarkdown("**", "**"),
2785
+ title: "Bold",
2786
+ children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "B" })
2787
+ }
2788
+ ),
2789
+ /* @__PURE__ */ jsxRuntime.jsx(
2790
+ ui.Button,
2791
+ {
2792
+ type: "button",
2793
+ variant: "secondary",
2794
+ size: "small",
2795
+ onClick: () => insertMarkdown("*", "*"),
2796
+ title: "Italic",
2797
+ children: /* @__PURE__ */ jsxRuntime.jsx("em", { children: "I" })
2798
+ }
2799
+ ),
2800
+ /* @__PURE__ */ jsxRuntime.jsx(
2801
+ ui.Button,
2802
+ {
2803
+ type: "button",
2804
+ variant: "secondary",
2805
+ size: "small",
2806
+ onClick: () => insertMarkdown("`", "`"),
2807
+ title: "Inline Code",
2808
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.CommandLine, { className: "h-4 w-4" })
2809
+ }
2810
+ ),
2811
+ /* @__PURE__ */ jsxRuntime.jsx(
2812
+ ui.Button,
2813
+ {
2814
+ type: "button",
2815
+ variant: "secondary",
2816
+ size: "small",
2817
+ onClick: () => insertMarkdown("## "),
2818
+ title: "Heading",
2819
+ children: "H2"
2820
+ }
2821
+ ),
2822
+ /* @__PURE__ */ jsxRuntime.jsx(
2823
+ ui.Button,
2824
+ {
2825
+ type: "button",
2826
+ variant: "secondary",
2827
+ size: "small",
2828
+ onClick: () => insertMarkdown("### "),
2829
+ title: "Heading",
2830
+ children: "H3"
2831
+ }
2832
+ ),
2833
+ /* @__PURE__ */ jsxRuntime.jsx(
2834
+ ui.Button,
2835
+ {
2836
+ type: "button",
2837
+ variant: "secondary",
2838
+ size: "small",
2839
+ onClick: () => insertMarkdown("[", "](url)"),
2840
+ title: "Link",
2841
+ children: "Link"
2842
+ }
2843
+ ),
2844
+ /* @__PURE__ */ jsxRuntime.jsx(
2845
+ ui.Button,
2846
+ {
2847
+ type: "button",
2848
+ variant: "secondary",
2849
+ size: "small",
2850
+ onClick: () => insertMarkdown("![alt](", ")"),
2851
+ title: "Image",
2852
+ children: "Image"
2853
+ }
2854
+ ),
2855
+ /* @__PURE__ */ jsxRuntime.jsx(
2856
+ ui.Button,
2857
+ {
2858
+ type: "button",
2859
+ variant: "secondary",
2860
+ size: "small",
2861
+ onClick: () => insertMarkdown("```\n", "\n```"),
2862
+ title: "Code Block",
2863
+ children: "Code"
2864
+ }
2865
+ ),
2866
+ /* @__PURE__ */ jsxRuntime.jsx(
2867
+ ui.Button,
2868
+ {
2869
+ type: "button",
2870
+ variant: "secondary",
2871
+ size: "small",
2872
+ onClick: () => insertMarkdown("- "),
2873
+ title: "List",
2874
+ children: "List"
2875
+ }
2876
+ )
2877
+ ] }),
2878
+ /* @__PURE__ */ jsxRuntime.jsx(
2879
+ ui.Textarea,
2880
+ {
2881
+ id: `markdown-${label}`,
2882
+ value,
2883
+ onChange: (e2) => onChange(e2.target.value),
2884
+ placeholder,
2885
+ rows,
2886
+ className: "font-mono text-sm"
2887
+ }
2888
+ )
2889
+ ] }) }),
2890
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "preview", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
2891
+ "div",
2892
+ {
2893
+ className: ui.clx(
2894
+ "min-h-[200px] rounded-lg border border-ui-border-base bg-ui-bg-subtle p-4",
2895
+ "prose prose-sm max-w-none"
2896
+ ),
2897
+ dangerouslySetInnerHTML: {
2898
+ __html: value ? renderMarkdownPreview(value) : '<p class="text-ui-fg-subtle">Nothing to preview</p>'
2899
+ }
2900
+ }
2901
+ ) })
2902
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2903
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
2904
+ /* @__PURE__ */ jsxRuntime.jsx(
2905
+ ui.Button,
2906
+ {
2907
+ type: "button",
2908
+ variant: "secondary",
2909
+ size: "small",
2910
+ onClick: () => insertMarkdown("**", "**"),
2911
+ title: "Bold",
2912
+ children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "B" })
2913
+ }
2914
+ ),
2915
+ /* @__PURE__ */ jsxRuntime.jsx(
2916
+ ui.Button,
2917
+ {
2918
+ type: "button",
2919
+ variant: "secondary",
2920
+ size: "small",
2921
+ onClick: () => insertMarkdown("*", "*"),
2922
+ title: "Italic",
2923
+ children: /* @__PURE__ */ jsxRuntime.jsx("em", { children: "I" })
2924
+ }
2925
+ ),
2926
+ /* @__PURE__ */ jsxRuntime.jsx(
2927
+ ui.Button,
2928
+ {
2929
+ type: "button",
2930
+ variant: "secondary",
2931
+ size: "small",
2932
+ onClick: () => insertMarkdown("`", "`"),
2933
+ title: "Inline Code",
2934
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.CommandLine, { className: "h-4 w-4" })
2935
+ }
2936
+ ),
2937
+ /* @__PURE__ */ jsxRuntime.jsx(
2938
+ ui.Button,
2939
+ {
2940
+ type: "button",
2941
+ variant: "secondary",
2942
+ size: "small",
2943
+ onClick: () => insertMarkdown("```\n", "\n```"),
2944
+ title: "Code Block",
2945
+ children: "Code"
2946
+ }
2947
+ )
2948
+ ] }),
2949
+ /* @__PURE__ */ jsxRuntime.jsx(
2950
+ ui.Textarea,
2951
+ {
2952
+ id: `markdown-${label}`,
2953
+ value,
2954
+ onChange: (e2) => onChange(e2.target.value),
2955
+ placeholder,
2956
+ rows,
2957
+ className: "font-mono text-sm"
2958
+ }
2959
+ )
2960
+ ] }),
2961
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-ui-fg-subtle", children: "Supports Markdown formatting: **bold**, *italic*, `code`, ```code blocks```, [links](url), ![images](url), and lists" })
2962
+ ] });
2963
+ };
2964
+ const CampaignImageUploader = ({
2965
+ campaignId,
2966
+ imageType,
2967
+ currentImageUrl,
2968
+ onClose,
2969
+ onSuccess
2970
+ }) => {
2971
+ const [displayImage, setDisplayImage] = React.useState(currentImageUrl || null);
2972
+ const [isUploading, setIsUploading] = React.useState(false);
2973
+ const [isDragging, setIsDragging] = React.useState(false);
2974
+ const [error, setError] = React.useState(null);
2975
+ const [previewUrl, setPreviewUrl] = React.useState(null);
2976
+ const isThumbnail = imageType === "thumbnail";
2977
+ const maxSize = 5;
2978
+ const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
2979
+ const formatFileTypes = () => {
2980
+ return "JPEG, PNG, GIF, or WebP";
2981
+ };
2982
+ const validateFile = (file) => {
2983
+ if (!allowedTypes.includes(file.type)) {
2984
+ setError(`Please upload a valid image file (${formatFileTypes()})`);
2985
+ return false;
2986
+ }
2987
+ if (file.size > maxSize * 1024 * 1024) {
2988
+ setError(`File size must be less than ${maxSize}MB`);
2989
+ return false;
2990
+ }
2991
+ return true;
2992
+ };
2993
+ const handleFileUpload = async (file) => {
2994
+ var _a, _b;
2995
+ if (!validateFile(file)) return;
2996
+ setError(null);
2997
+ const reader = new FileReader();
2998
+ reader.onloadend = () => {
2999
+ setPreviewUrl(reader.result);
3000
+ };
3001
+ reader.readAsDataURL(file);
3002
+ setIsUploading(true);
3003
+ const formData = new FormData();
3004
+ formData.append("file", file);
3005
+ try {
3006
+ const response = await fetch(
3007
+ `/admin/campaigns/${campaignId}/${imageType}`,
3008
+ {
3009
+ method: "POST",
3010
+ body: formData,
3011
+ credentials: "include"
3012
+ }
3013
+ );
3014
+ if (response.ok) {
3015
+ const result = await response.json();
3016
+ const newImageUrl = imageType === "image" ? (_a = result.campaign_detail) == null ? void 0 : _a.image_url : (_b = result.campaign_detail) == null ? void 0 : _b.thumbnail_url;
3017
+ setDisplayImage(newImageUrl);
3018
+ setPreviewUrl(null);
3019
+ onSuccess == null ? void 0 : onSuccess();
3020
+ setTimeout(() => {
3021
+ onClose();
3022
+ }, 1e3);
3023
+ } else {
3024
+ const errorData = await response.json();
3025
+ setError(errorData.error || `Failed to upload ${imageType}`);
3026
+ setPreviewUrl(null);
3027
+ }
3028
+ } catch (err) {
3029
+ setError(`Error uploading ${imageType}`);
3030
+ setPreviewUrl(null);
3031
+ console.error(`Error uploading ${imageType}:`, err);
3032
+ } finally {
3033
+ setIsUploading(false);
3034
+ }
3035
+ };
3036
+ const handleDelete = async () => {
3037
+ if (!confirm(`Are you sure you want to delete the ${imageType}?`)) return;
3038
+ setIsUploading(true);
3039
+ setError(null);
3040
+ try {
3041
+ const response = await fetch(
3042
+ `/admin/campaigns/${campaignId}/${imageType}`,
3043
+ {
3044
+ method: "DELETE",
3045
+ credentials: "include"
3046
+ }
3047
+ );
3048
+ if (response.ok) {
3049
+ setDisplayImage(null);
3050
+ setPreviewUrl(null);
3051
+ onSuccess == null ? void 0 : onSuccess();
3052
+ setTimeout(() => {
3053
+ onClose();
3054
+ }, 500);
3055
+ } else {
3056
+ const errorData = await response.json();
3057
+ setError(errorData.error || `Failed to delete ${imageType}`);
3058
+ }
3059
+ } catch (err) {
3060
+ setError(`Error deleting ${imageType}`);
3061
+ console.error(`Error deleting ${imageType}:`, err);
3062
+ } finally {
3063
+ setIsUploading(false);
3064
+ }
3065
+ };
3066
+ const handleDragEnter = React.useCallback((e2) => {
3067
+ e2.preventDefault();
3068
+ e2.stopPropagation();
3069
+ setIsDragging(true);
3070
+ }, []);
3071
+ const handleDragLeave = React.useCallback((e2) => {
3072
+ e2.preventDefault();
3073
+ e2.stopPropagation();
3074
+ setIsDragging(false);
3075
+ }, []);
3076
+ const handleDragOver = React.useCallback((e2) => {
3077
+ e2.preventDefault();
3078
+ e2.stopPropagation();
3079
+ }, []);
3080
+ const handleDrop = React.useCallback((e2) => {
3081
+ var _a;
3082
+ e2.preventDefault();
3083
+ e2.stopPropagation();
3084
+ setIsDragging(false);
3085
+ const file = (_a = e2.dataTransfer.files) == null ? void 0 : _a[0];
3086
+ if (file) {
3087
+ handleFileUpload(file);
3088
+ }
3089
+ }, []);
3090
+ const imageToDisplay = previewUrl || displayImage;
3091
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-2xl rounded-lg bg-ui-bg-base p-6", children: [
3092
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
3093
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3094
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Heading, { level: "h2", children: [
3095
+ "Upload Campaign ",
3096
+ isThumbnail ? "Thumbnail" : "Image"
3097
+ ] }),
3098
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: isThumbnail ? "Thumbnail for campaign listing" : "Main campaign image" })
3099
+ ] }),
3100
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(icons.X, {}) })
3101
+ ] }),
3102
+ error && /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "error", dismissible: true, className: "mb-4", children: error }),
3103
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
3104
+ imageToDisplay ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
3105
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-subtle", children: [
3106
+ /* @__PURE__ */ jsxRuntime.jsx(
3107
+ "img",
3108
+ {
3109
+ src: imageToDisplay,
3110
+ alt: `Campaign ${imageType}`,
3111
+ className: ui.clx(
3112
+ "w-full object-contain",
3113
+ isThumbnail ? "h-32" : "h-64"
3114
+ )
3115
+ }
3116
+ ),
3117
+ isUploading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-white", children: "Uploading..." }) })
3118
+ ] }),
3119
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
3120
+ /* @__PURE__ */ jsxRuntime.jsxs(
3121
+ ui.Button,
3122
+ {
3123
+ variant: "secondary",
3124
+ disabled: isUploading,
3125
+ onClick: () => {
3126
+ var _a;
3127
+ return (_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click();
3128
+ },
3129
+ children: [
3130
+ /* @__PURE__ */ jsxRuntime.jsx(icons.CloudArrowUp, { className: "mr-2" }),
3131
+ "Replace"
3132
+ ]
3133
+ }
3134
+ ),
3135
+ /* @__PURE__ */ jsxRuntime.jsxs(
3136
+ ui.Button,
3137
+ {
3138
+ variant: "danger",
3139
+ disabled: isUploading,
3140
+ onClick: handleDelete,
3141
+ children: [
3142
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "mr-2" }),
3143
+ "Delete"
3144
+ ]
3145
+ }
3146
+ )
3147
+ ] })
3148
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
3149
+ "div",
3150
+ {
3151
+ className: ui.clx(
3152
+ "flex flex-col items-center justify-center rounded-lg border-2 border-dashed transition-colors",
3153
+ isThumbnail ? "h-48" : "h-64",
3154
+ isDragging ? "border-ui-border-interactive bg-ui-bg-highlight" : "border-ui-border-base bg-ui-bg-subtle",
3155
+ !isUploading && "cursor-pointer"
3156
+ ),
3157
+ onDragEnter: handleDragEnter,
3158
+ onDragLeave: handleDragLeave,
3159
+ onDragOver: handleDragOver,
3160
+ onDrop: handleDrop,
3161
+ onClick: () => {
3162
+ var _a;
3163
+ return !isUploading && ((_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click());
3164
+ },
3165
+ children: [
3166
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }),
3167
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-2 text-lg font-medium", children: isDragging ? `Drop ${imageType} here` : `Upload ${isThumbnail ? "Thumbnail" : "Image"}` }),
3168
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-4 text-center text-ui-fg-subtle", children: "Drag and drop an image here, or click to select" }),
3169
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-sm text-ui-fg-subtle", children: [
3170
+ formatFileTypes(),
3171
+ " • Max ",
3172
+ maxSize,
3173
+ "MB"
3174
+ ] }),
3175
+ isThumbnail && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: 16:9 aspect ratio, minimum 400x225px" }),
3176
+ !isThumbnail && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: High resolution, minimum 1200x600px" }),
3177
+ isUploading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Uploading..." }) })
3178
+ ]
3179
+ }
3180
+ ),
3181
+ /* @__PURE__ */ jsxRuntime.jsx(
3182
+ "input",
3183
+ {
3184
+ id: `${imageType}-file-input`,
3185
+ type: "file",
3186
+ accept: allowedTypes.join(","),
3187
+ onChange: (e2) => {
3188
+ var _a;
3189
+ const file = (_a = e2.target.files) == null ? void 0 : _a[0];
3190
+ if (file) handleFileUpload(file);
3191
+ e2.target.value = "";
3192
+ },
3193
+ className: "hidden"
3194
+ }
3195
+ )
3196
+ ] }),
3197
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 flex items-center justify-between border-t pt-4", children: [
3198
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: isThumbnail ? "Thumbnail will be displayed in campaign listings" : "Main image for the campaign detail page" }),
3199
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: onClose, children: "Close" })
3200
+ ] })
3201
+ ] }) });
3202
+ };
3203
+ const CampaignDetailForm = ({
3204
+ campaignId,
3205
+ campaignName
3206
+ }) => {
3207
+ const [campaignDetail, setCampaignDetail] = React.useState(
3208
+ null
3209
+ );
3210
+ const [isLoading, setIsLoading] = React.useState(true);
3211
+ const [isSaving, setIsSaving] = React.useState(false);
3212
+ const [showImageUploader, setShowImageUploader] = React.useState(null);
3213
+ const [formData, setFormData] = React.useState({
3214
+ detail_content: "",
3215
+ terms_and_conditions: "",
3216
+ meta_title: "",
3217
+ meta_description: "",
3218
+ meta_keywords: "",
3219
+ link_url: "",
3220
+ link_text: "",
3221
+ display_order: 0
3222
+ });
3223
+ React.useEffect(() => {
3224
+ fetchCampaignDetail();
3225
+ }, [campaignId]);
3226
+ const fetchCampaignDetail = async () => {
3227
+ setIsLoading(true);
3228
+ try {
3229
+ const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
3230
+ credentials: "include"
3231
+ });
3232
+ if (response.ok) {
3233
+ const data = await response.json();
3234
+ if (data.campaign_detail) {
3235
+ console.log("Campaign detail loaded:", {
3236
+ image_url: data.campaign_detail.image_url,
3237
+ thumbnail_url: data.campaign_detail.thumbnail_url,
3238
+ decoded_image: data.campaign_detail.image_url ? decodeURIComponent(data.campaign_detail.image_url) : null,
3239
+ decoded_thumbnail: data.campaign_detail.thumbnail_url ? decodeURIComponent(data.campaign_detail.thumbnail_url) : null
3240
+ });
3241
+ setCampaignDetail(data.campaign_detail);
3242
+ setFormData({
3243
+ detail_content: data.campaign_detail.detail_content || "",
3244
+ terms_and_conditions: data.campaign_detail.terms_and_conditions || "",
3245
+ meta_title: data.campaign_detail.meta_title || "",
3246
+ meta_description: data.campaign_detail.meta_description || "",
3247
+ meta_keywords: data.campaign_detail.meta_keywords || "",
3248
+ link_url: data.campaign_detail.link_url || "",
3249
+ link_text: data.campaign_detail.link_text || "",
3250
+ display_order: data.campaign_detail.display_order || 0
3251
+ });
3252
+ }
3253
+ } else if (response.status !== 404) {
3254
+ console.error("Failed to fetch campaign detail");
3255
+ }
3256
+ } catch (error) {
3257
+ console.error("Error fetching campaign detail:", error);
3258
+ } finally {
3259
+ setIsLoading(false);
3260
+ }
3261
+ };
3262
+ const handleSave = async () => {
3263
+ setIsSaving(true);
3264
+ try {
3265
+ const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
3266
+ method: "POST",
3267
+ headers: {
3268
+ "Content-Type": "application/json"
3269
+ },
3270
+ body: JSON.stringify(formData),
3271
+ credentials: "include"
3272
+ });
3273
+ if (response.ok) {
3274
+ const data = await response.json();
3275
+ setCampaignDetail(data.campaign_detail);
3276
+ ui.toast.success("Campaign details saved successfully");
3277
+ } else {
3278
+ const errorData = await response.json();
3279
+ ui.toast.error(errorData.error || "Failed to save campaign details");
3280
+ }
3281
+ } catch (error) {
3282
+ console.error("Error saving campaign detail:", error);
3283
+ ui.toast.error("Error saving campaign details");
3284
+ } finally {
3285
+ setIsSaving(false);
3286
+ }
3287
+ };
3288
+ if (isLoading) {
3289
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-8 text-center", children: "Loading campaign details..." }) });
3290
+ }
3291
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3292
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "space-y-6", children: [
3293
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
3294
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3295
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Campaign Details" }),
3296
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-ui-fg-subtle", children: campaignName })
3297
+ ] }),
3298
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSave, isLoading: isSaving, children: "Save Details" })
3299
+ ] }),
3300
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
3301
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Images" }),
3302
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
3303
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
3304
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Main Campaign Image" }),
3305
+ /* @__PURE__ */ jsxRuntime.jsx(
3306
+ "div",
3307
+ {
3308
+ className: ui.clx(
3309
+ "relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
3310
+ (campaignDetail == null ? void 0 : campaignDetail.image_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
3311
+ ),
3312
+ children: (campaignDetail == null ? void 0 : campaignDetail.image_url) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3313
+ /* @__PURE__ */ jsxRuntime.jsx(
3314
+ "img",
3315
+ {
3316
+ src: campaignDetail.image_url,
3317
+ alt: "Campaign",
3318
+ className: "h-full w-full object-cover",
3319
+ onError: (e2) => {
3320
+ console.error(
3321
+ "Failed to load image:",
3322
+ campaignDetail.image_url
3323
+ );
3324
+ e2.currentTarget.style.display = "none";
3325
+ }
3326
+ }
3327
+ ),
3328
+ /* @__PURE__ */ jsxRuntime.jsxs(
3329
+ ui.Button,
3330
+ {
3331
+ variant: "secondary",
3332
+ size: "small",
3333
+ className: "absolute bottom-2 right-2",
3334
+ onClick: () => setShowImageUploader("image"),
3335
+ children: [
3336
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "mr-1" }),
3337
+ "Change"
3338
+ ]
3339
+ }
3340
+ )
3341
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
3342
+ ui.Button,
3343
+ {
3344
+ variant: "secondary",
3345
+ onClick: () => setShowImageUploader("image"),
3346
+ children: [
3347
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "mr-2" }),
3348
+ "Upload Image"
3349
+ ]
3350
+ }
3351
+ )
3352
+ }
3353
+ )
3354
+ ] }),
3355
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
3356
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Thumbnail" }),
3357
+ /* @__PURE__ */ jsxRuntime.jsx(
3358
+ "div",
3359
+ {
3360
+ className: ui.clx(
3361
+ "relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
3362
+ (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
3363
+ ),
3364
+ children: (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3365
+ /* @__PURE__ */ jsxRuntime.jsx(
3366
+ "img",
3367
+ {
3368
+ src: campaignDetail.thumbnail_url,
3369
+ alt: "Thumbnail",
3370
+ className: "h-full w-full object-cover",
3371
+ onError: (e2) => {
3372
+ console.error(
3373
+ "Failed to load thumbnail:",
3374
+ campaignDetail.thumbnail_url
3375
+ );
3376
+ e2.currentTarget.style.display = "none";
3377
+ }
3378
+ }
3379
+ ),
3380
+ /* @__PURE__ */ jsxRuntime.jsxs(
3381
+ ui.Button,
3382
+ {
3383
+ variant: "secondary",
3384
+ size: "small",
3385
+ className: "absolute bottom-2 right-2",
3386
+ onClick: () => setShowImageUploader("thumbnail"),
3387
+ children: [
3388
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "mr-1" }),
3389
+ "Change"
3390
+ ]
3391
+ }
3392
+ )
3393
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
3394
+ ui.Button,
3395
+ {
3396
+ variant: "secondary",
3397
+ onClick: () => setShowImageUploader("thumbnail"),
3398
+ children: [
3399
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "mr-2" }),
3400
+ "Upload Thumbnail"
3401
+ ]
3402
+ }
3403
+ )
3404
+ }
3405
+ )
3406
+ ] })
3407
+ ] })
3408
+ ] }),
3409
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx(
3410
+ MarkdownEditor,
3411
+ {
3412
+ label: "Campaign Detail Content",
3413
+ value: formData.detail_content,
3414
+ onChange: (value) => setFormData({ ...formData, detail_content: value }),
3415
+ placeholder: "Enter campaign details in Markdown format. You can include images, links, code blocks, and more...",
3416
+ helpText: "Supports Markdown and code syntax",
3417
+ rows: 12,
3418
+ showPreview: true
3419
+ }
3420
+ ) }),
3421
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx(
3422
+ MarkdownEditor,
3423
+ {
3424
+ label: "Terms and Conditions",
3425
+ value: formData.terms_and_conditions,
3426
+ onChange: (value) => setFormData({ ...formData, terms_and_conditions: value }),
3427
+ placeholder: "Enter terms and conditions for this campaign...",
3428
+ helpText: "Optional - Campaign specific terms",
3429
+ rows: 8,
3430
+ showPreview: true
3431
+ }
3432
+ ) }),
3433
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
3434
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Call to Action Link" }),
3435
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
3436
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
3437
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "link-url", children: "Link URL" }),
3438
+ /* @__PURE__ */ jsxRuntime.jsx(
3439
+ ui.Input,
3440
+ {
3441
+ id: "link-url",
3442
+ type: "url",
3443
+ value: formData.link_url,
3444
+ onChange: (e2) => setFormData({ ...formData, link_url: e2.target.value }),
3445
+ placeholder: "https://example.com/campaign"
3446
+ }
3447
+ )
3448
+ ] }),
3449
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
3450
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "link-text", children: "Link Text" }),
3451
+ /* @__PURE__ */ jsxRuntime.jsx(
3452
+ ui.Input,
3453
+ {
3454
+ id: "link-text",
3455
+ value: formData.link_text,
3456
+ onChange: (e2) => setFormData({ ...formData, link_text: e2.target.value }),
3457
+ placeholder: "Shop Now"
3458
+ }
3459
+ )
3460
+ ] })
3461
+ ] })
3462
+ ] }),
3463
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
3464
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "SEO & Metadata" }),
3465
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
3466
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
3467
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "meta-title", children: "Meta Title" }),
3468
+ /* @__PURE__ */ jsxRuntime.jsx(
3469
+ ui.Input,
3470
+ {
3471
+ id: "meta-title",
3472
+ value: formData.meta_title,
3473
+ onChange: (e2) => setFormData({ ...formData, meta_title: e2.target.value }),
3474
+ placeholder: "Campaign meta title for SEO"
3475
+ }
3476
+ )
3477
+ ] }),
3478
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
3479
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "meta-description", children: "Meta Description" }),
3480
+ /* @__PURE__ */ jsxRuntime.jsx(
3481
+ ui.Input,
3482
+ {
3483
+ id: "meta-description",
3484
+ value: formData.meta_description,
3485
+ onChange: (e2) => setFormData({
3486
+ ...formData,
3487
+ meta_description: e2.target.value
3488
+ }),
3489
+ placeholder: "Campaign meta description for SEO"
3490
+ }
3491
+ )
3492
+ ] }),
3493
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
3494
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "meta-keywords", children: "Meta Keywords" }),
3495
+ /* @__PURE__ */ jsxRuntime.jsx(
3496
+ ui.Input,
3497
+ {
3498
+ id: "meta-keywords",
3499
+ value: formData.meta_keywords,
3500
+ onChange: (e2) => setFormData({
3501
+ ...formData,
3502
+ meta_keywords: e2.target.value
3503
+ }),
3504
+ placeholder: "keyword1, keyword2, keyword3"
3505
+ }
3506
+ ),
3507
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Comma-separated keywords for SEO" })
3508
+ ] }),
3509
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
3510
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "display-order", children: "Display Order" }),
3511
+ /* @__PURE__ */ jsxRuntime.jsx(
3512
+ ui.Input,
3513
+ {
3514
+ id: "display-order",
3515
+ type: "number",
3516
+ value: formData.display_order,
3517
+ onChange: (e2) => setFormData({
3518
+ ...formData,
3519
+ display_order: parseInt(e2.target.value) || 0
3520
+ }),
3521
+ placeholder: "0"
3522
+ }
3523
+ ),
3524
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Lower numbers appear first in listings" })
3525
+ ] })
3526
+ ] })
3527
+ ] }),
3528
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end border-t pt-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSave, isLoading: isSaving, children: "Save Campaign Details" }) })
3529
+ ] }),
3530
+ showImageUploader && /* @__PURE__ */ jsxRuntime.jsx(
3531
+ CampaignImageUploader,
3532
+ {
3533
+ campaignId,
3534
+ imageType: showImageUploader,
3535
+ currentImageUrl: showImageUploader === "image" ? campaignDetail == null ? void 0 : campaignDetail.image_url : campaignDetail == null ? void 0 : campaignDetail.thumbnail_url,
3536
+ onClose: () => setShowImageUploader(null),
3537
+ onSuccess: fetchCampaignDetail
3538
+ }
3539
+ )
3540
+ ] });
3541
+ };
2353
3542
  const FlashSaleDetail = () => {
2354
3543
  const { id } = reactRouterDom.useParams();
2355
3544
  const navigate = reactRouterDom.useNavigate();
@@ -2402,18 +3591,42 @@ const FlashSaleDetail = () => {
2402
3591
  starts_at: data.starts_at,
2403
3592
  ends_at: data.ends_at
2404
3593
  };
2405
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2406
- /* @__PURE__ */ jsxRuntime.jsx(
2407
- FlashSaleForm,
2408
- {
2409
- initialData: campaignData,
2410
- initialProducts: new Map(data.products.map((product) => [product.product.id, product])),
2411
- onSubmit: handleSubmit,
2412
- onCancel: () => navigate("/flash-sales"),
2413
- disabled: !isEditing
2414
- }
2415
- ),
2416
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => setIsEditing((prev) => !prev), children: "Toggle Edit" })
3594
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
3595
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
3596
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3597
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold mb-1", children: data.name }),
3598
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle", children: "Manage campaign settings and content" })
3599
+ ] }),
3600
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
3601
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/flash-sales"), children: "Back to Campaigns" }),
3602
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
3603
+ ] })
3604
+ ] }),
3605
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { defaultValue: "settings", children: [
3606
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
3607
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "settings", children: "Campaign Settings" }),
3608
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "details", children: "Campaign Details" })
3609
+ ] }),
3610
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
3611
+ FlashSaleForm,
3612
+ {
3613
+ initialData: campaignData,
3614
+ initialProducts: new Map(
3615
+ data.products.map((product) => [product.product.id, product])
3616
+ ),
3617
+ onSubmit: handleSubmit,
3618
+ onCancel: () => navigate("/flash-sales"),
3619
+ disabled: !isEditing
3620
+ }
3621
+ ) }),
3622
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
3623
+ CampaignDetailForm,
3624
+ {
3625
+ campaignId: id || "",
3626
+ campaignName: data.name || ""
3627
+ }
3628
+ ) })
3629
+ ] })
2417
3630
  ] });
2418
3631
  };
2419
3632
  const config = adminSdk.defineRouteConfig({
@@ -2460,7 +3673,16 @@ const FlashSaleCreate = () => {
2460
3673
  }
2461
3674
  );
2462
3675
  };
2463
- const widgetModule = { widgets: [] };
3676
+ const widgetModule = { widgets: [
3677
+ {
3678
+ Component: CampaignDetailWidget,
3679
+ zone: ["promotion.details.side.after"]
3680
+ },
3681
+ {
3682
+ Component: CampaignStatsWidget,
3683
+ zone: ["campaign.list.before"]
3684
+ }
3685
+ ] };
2464
3686
  const routeModule = {
2465
3687
  routes: [
2466
3688
  {