@lodashventure/medusa-campaign 1.4.3 → 1.4.5
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.
- package/.medusa/server/src/admin/index.js +1560 -1560
- package/.medusa/server/src/admin/index.mjs +1562 -1562
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
|
|
3
|
-
import { Container, Heading, Text, Alert, Badge, Button, createDataTableColumnHelper, useDataTable, DataTable,
|
|
4
|
-
import { Sparkles, PhotoSolid, PencilSquare, Eye, Tag,
|
|
3
|
+
import { Container, Heading, Text, Alert, Badge, Button, createDataTableColumnHelper, useDataTable, DataTable, Label, Input, Textarea, Select, toast, Tabs, clx, Table, FocusModal } from "@medusajs/ui";
|
|
4
|
+
import { Sparkles, PhotoSolid, PencilSquare, Eye, Tag, InformationCircle, CommandLine, X, CloudArrowUp, Trash } from "@medusajs/icons";
|
|
5
5
|
import React, { useState, useEffect, useMemo, useCallback } from "react";
|
|
6
6
|
import { useNavigate, useParams } from "react-router-dom";
|
|
7
7
|
import dayjs from "dayjs";
|
|
@@ -194,102 +194,99 @@ const CampaignDetailWidget = ({
|
|
|
194
194
|
defineWidgetConfig({
|
|
195
195
|
zone: "promotion.details.side.after"
|
|
196
196
|
});
|
|
197
|
-
const
|
|
197
|
+
const useCoupons = ({ limit, offset }) => {
|
|
198
198
|
const [data, setData] = useState(null);
|
|
199
199
|
const [isLoading, setIsLoading] = useState(true);
|
|
200
200
|
const [error, setError] = useState(null);
|
|
201
|
-
const
|
|
201
|
+
const fetchCoupons = async () => {
|
|
202
202
|
setIsLoading(true);
|
|
203
203
|
setError(null);
|
|
204
204
|
try {
|
|
205
|
-
const response = await axios.get("/admin/
|
|
206
|
-
params:
|
|
205
|
+
const response = await axios.get("/admin/coupons", {
|
|
206
|
+
params: { limit, offset }
|
|
207
207
|
});
|
|
208
208
|
setData(response.data);
|
|
209
209
|
} catch (err) {
|
|
210
|
-
setError(
|
|
211
|
-
err instanceof Error ? err : new Error("Failed to fetch flash sales")
|
|
212
|
-
);
|
|
210
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch coupons"));
|
|
213
211
|
} finally {
|
|
214
212
|
setIsLoading(false);
|
|
215
213
|
}
|
|
216
214
|
};
|
|
217
215
|
useEffect(() => {
|
|
218
|
-
|
|
219
|
-
}, [
|
|
216
|
+
fetchCoupons();
|
|
217
|
+
}, [limit, offset]);
|
|
220
218
|
return {
|
|
221
219
|
data,
|
|
222
220
|
isLoading,
|
|
223
221
|
error,
|
|
224
|
-
refetch:
|
|
222
|
+
refetch: fetchCoupons
|
|
225
223
|
};
|
|
226
224
|
};
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
pageSize: limit,
|
|
232
|
-
pageIndex: 0
|
|
233
|
-
});
|
|
234
|
-
const offset = useMemo(() => {
|
|
235
|
-
return pagination.pageIndex * limit;
|
|
236
|
-
}, [pagination]);
|
|
237
|
-
const columnHelper2 = createDataTableColumnHelper();
|
|
238
|
-
const columns2 = [
|
|
239
|
-
columnHelper2.accessor("name", {
|
|
225
|
+
const columnHelper$1 = createDataTableColumnHelper();
|
|
226
|
+
const useCouponColumns = () => useMemo(
|
|
227
|
+
() => [
|
|
228
|
+
columnHelper$1.accessor("name", {
|
|
240
229
|
header: "Name",
|
|
241
230
|
cell: (info) => info.getValue()
|
|
242
231
|
}),
|
|
243
|
-
|
|
232
|
+
columnHelper$1.display({
|
|
233
|
+
id: "code",
|
|
234
|
+
header: "Code",
|
|
235
|
+
cell: (info) => {
|
|
236
|
+
var _a, _b;
|
|
237
|
+
return ((_b = (_a = info.row.original.promotions) == null ? void 0 : _a[0]) == null ? void 0 : _b.code) ?? "-";
|
|
238
|
+
}
|
|
239
|
+
}),
|
|
240
|
+
columnHelper$1.display({
|
|
241
|
+
id: "status",
|
|
244
242
|
header: "Status",
|
|
245
243
|
cell: (info) => {
|
|
246
|
-
const
|
|
247
|
-
if (!
|
|
244
|
+
const endsAt = info.row.original.ends_at;
|
|
245
|
+
if (!endsAt) {
|
|
248
246
|
return "";
|
|
249
247
|
}
|
|
250
|
-
const isActive = dayjs(
|
|
248
|
+
const isActive = dayjs(endsAt).isAfter(dayjs());
|
|
251
249
|
return /* @__PURE__ */ jsx(Badge, { color: isActive ? "green" : "grey", children: isActive ? "Active" : "Inactive" });
|
|
252
250
|
}
|
|
253
251
|
}),
|
|
254
|
-
|
|
252
|
+
columnHelper$1.accessor("starts_at", {
|
|
255
253
|
header: "Start Date",
|
|
256
|
-
cell: (info) =>
|
|
257
|
-
const date = info.getValue();
|
|
258
|
-
if (!date) {
|
|
259
|
-
return "";
|
|
260
|
-
}
|
|
261
|
-
return dayjs(date).format("YYYY-MM-DD HH:mm");
|
|
262
|
-
}
|
|
254
|
+
cell: (info) => info.getValue() ? dayjs(info.getValue()).format("YYYY-MM-DD HH:mm") : ""
|
|
263
255
|
}),
|
|
264
|
-
|
|
256
|
+
columnHelper$1.accessor("ends_at", {
|
|
265
257
|
header: "End Date",
|
|
266
|
-
cell: (info) =>
|
|
267
|
-
const date = info.getValue();
|
|
268
|
-
if (!date) {
|
|
269
|
-
return "";
|
|
270
|
-
}
|
|
271
|
-
return dayjs(date).format("YYYY-MM-DD HH:mm");
|
|
272
|
-
}
|
|
258
|
+
cell: (info) => info.getValue() ? dayjs(info.getValue()).format("YYYY-MM-DD HH:mm") : ""
|
|
273
259
|
})
|
|
274
|
-
]
|
|
275
|
-
|
|
260
|
+
],
|
|
261
|
+
[]
|
|
262
|
+
);
|
|
263
|
+
const CouponPage = () => {
|
|
264
|
+
const navigate = useNavigate();
|
|
265
|
+
const limit = 20;
|
|
266
|
+
const [pagination, setPagination] = useState({
|
|
267
|
+
pageSize: limit,
|
|
268
|
+
pageIndex: 0
|
|
269
|
+
});
|
|
270
|
+
const offset = pagination.pageIndex * limit;
|
|
271
|
+
const { data } = useCoupons({ limit, offset });
|
|
272
|
+
const columns2 = useCouponColumns();
|
|
276
273
|
const table = useDataTable({
|
|
277
|
-
data: (data == null ? void 0 : data.campaigns)
|
|
274
|
+
data: (data == null ? void 0 : data.campaigns) ?? [],
|
|
278
275
|
columns: columns2,
|
|
279
|
-
getRowId: (
|
|
276
|
+
getRowId: (row) => row.id,
|
|
280
277
|
pagination: {
|
|
281
278
|
state: pagination,
|
|
282
279
|
onPaginationChange: setPagination
|
|
283
280
|
},
|
|
284
|
-
rowCount: (data == null ? void 0 : data.count)
|
|
281
|
+
rowCount: (data == null ? void 0 : data.count) ?? 0,
|
|
285
282
|
onRowClick: (_, row) => {
|
|
286
|
-
navigate(`/
|
|
283
|
+
navigate(`/coupons/${row.id}`);
|
|
287
284
|
}
|
|
288
285
|
});
|
|
289
286
|
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
290
287
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
291
|
-
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "
|
|
292
|
-
/* @__PURE__ */ jsx(Button, { onClick: () => navigate("/
|
|
288
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "Coupons" }),
|
|
289
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => navigate("/coupons/create"), children: "Create Coupon" })
|
|
293
290
|
] }),
|
|
294
291
|
/* @__PURE__ */ jsxs(DataTable, { instance: table, children: [
|
|
295
292
|
/* @__PURE__ */ jsx(DataTable.Table, {}),
|
|
@@ -297,106 +294,109 @@ const FlashSalePage = () => {
|
|
|
297
294
|
] })
|
|
298
295
|
] });
|
|
299
296
|
};
|
|
300
|
-
const
|
|
301
|
-
return /* @__PURE__ */ jsx(
|
|
297
|
+
const Coupons = () => {
|
|
298
|
+
return /* @__PURE__ */ jsx(CouponPage, {});
|
|
302
299
|
};
|
|
303
300
|
const config$3 = defineRouteConfig({
|
|
304
|
-
label: "
|
|
305
|
-
icon:
|
|
301
|
+
label: "Coupons",
|
|
302
|
+
icon: Tag
|
|
306
303
|
});
|
|
307
|
-
const
|
|
304
|
+
const useFlashSales = (pagination) => {
|
|
308
305
|
const [data, setData] = useState(null);
|
|
309
306
|
const [isLoading, setIsLoading] = useState(true);
|
|
310
307
|
const [error, setError] = useState(null);
|
|
311
|
-
const
|
|
308
|
+
const fetchFlashSales = async () => {
|
|
312
309
|
setIsLoading(true);
|
|
313
310
|
setError(null);
|
|
314
311
|
try {
|
|
315
|
-
const response = await axios.get("/admin/
|
|
316
|
-
params:
|
|
312
|
+
const response = await axios.get("/admin/flash-sales", {
|
|
313
|
+
params: pagination
|
|
317
314
|
});
|
|
318
315
|
setData(response.data);
|
|
319
316
|
} catch (err) {
|
|
320
|
-
setError(
|
|
317
|
+
setError(
|
|
318
|
+
err instanceof Error ? err : new Error("Failed to fetch flash sales")
|
|
319
|
+
);
|
|
321
320
|
} finally {
|
|
322
321
|
setIsLoading(false);
|
|
323
322
|
}
|
|
324
323
|
};
|
|
325
324
|
useEffect(() => {
|
|
326
|
-
|
|
327
|
-
}, [limit, offset]);
|
|
325
|
+
fetchFlashSales();
|
|
326
|
+
}, [pagination.limit, pagination.offset]);
|
|
328
327
|
return {
|
|
329
328
|
data,
|
|
330
329
|
isLoading,
|
|
331
330
|
error,
|
|
332
|
-
refetch:
|
|
331
|
+
refetch: fetchFlashSales
|
|
333
332
|
};
|
|
334
333
|
};
|
|
335
|
-
const
|
|
336
|
-
const
|
|
337
|
-
|
|
338
|
-
|
|
334
|
+
const FlashSalePage = () => {
|
|
335
|
+
const navigate = useNavigate();
|
|
336
|
+
const limit = 20;
|
|
337
|
+
const [pagination, setPagination] = useState({
|
|
338
|
+
pageSize: limit,
|
|
339
|
+
pageIndex: 0
|
|
340
|
+
});
|
|
341
|
+
const offset = useMemo(() => {
|
|
342
|
+
return pagination.pageIndex * limit;
|
|
343
|
+
}, [pagination]);
|
|
344
|
+
const columnHelper2 = createDataTableColumnHelper();
|
|
345
|
+
const columns2 = [
|
|
346
|
+
columnHelper2.accessor("name", {
|
|
339
347
|
header: "Name",
|
|
340
348
|
cell: (info) => info.getValue()
|
|
341
349
|
}),
|
|
342
|
-
|
|
343
|
-
id: "code",
|
|
344
|
-
header: "Code",
|
|
345
|
-
cell: (info) => {
|
|
346
|
-
var _a, _b;
|
|
347
|
-
return ((_b = (_a = info.row.original.promotions) == null ? void 0 : _a[0]) == null ? void 0 : _b.code) ?? "-";
|
|
348
|
-
}
|
|
349
|
-
}),
|
|
350
|
-
columnHelper$1.display({
|
|
351
|
-
id: "status",
|
|
350
|
+
columnHelper2.accessor("ends_at", {
|
|
352
351
|
header: "Status",
|
|
353
352
|
cell: (info) => {
|
|
354
|
-
const
|
|
355
|
-
if (!
|
|
353
|
+
const date = info.getValue();
|
|
354
|
+
if (!date) {
|
|
356
355
|
return "";
|
|
357
356
|
}
|
|
358
|
-
const isActive = dayjs(
|
|
357
|
+
const isActive = dayjs(date).isAfter(dayjs());
|
|
359
358
|
return /* @__PURE__ */ jsx(Badge, { color: isActive ? "green" : "grey", children: isActive ? "Active" : "Inactive" });
|
|
360
359
|
}
|
|
361
360
|
}),
|
|
362
|
-
|
|
361
|
+
columnHelper2.accessor("starts_at", {
|
|
363
362
|
header: "Start Date",
|
|
364
|
-
cell: (info) =>
|
|
363
|
+
cell: (info) => {
|
|
364
|
+
const date = info.getValue();
|
|
365
|
+
if (!date) {
|
|
366
|
+
return "";
|
|
367
|
+
}
|
|
368
|
+
return dayjs(date).format("YYYY-MM-DD HH:mm");
|
|
369
|
+
}
|
|
365
370
|
}),
|
|
366
|
-
|
|
371
|
+
columnHelper2.accessor("ends_at", {
|
|
367
372
|
header: "End Date",
|
|
368
|
-
cell: (info) =>
|
|
373
|
+
cell: (info) => {
|
|
374
|
+
const date = info.getValue();
|
|
375
|
+
if (!date) {
|
|
376
|
+
return "";
|
|
377
|
+
}
|
|
378
|
+
return dayjs(date).format("YYYY-MM-DD HH:mm");
|
|
379
|
+
}
|
|
369
380
|
})
|
|
370
|
-
]
|
|
371
|
-
|
|
372
|
-
);
|
|
373
|
-
const CouponPage = () => {
|
|
374
|
-
const navigate = useNavigate();
|
|
375
|
-
const limit = 20;
|
|
376
|
-
const [pagination, setPagination] = useState({
|
|
377
|
-
pageSize: limit,
|
|
378
|
-
pageIndex: 0
|
|
379
|
-
});
|
|
380
|
-
const offset = pagination.pageIndex * limit;
|
|
381
|
-
const { data } = useCoupons({ limit, offset });
|
|
382
|
-
const columns2 = useCouponColumns();
|
|
381
|
+
];
|
|
382
|
+
const { data } = useFlashSales({ limit, offset });
|
|
383
383
|
const table = useDataTable({
|
|
384
|
-
data: (data == null ? void 0 : data.campaigns)
|
|
384
|
+
data: (data == null ? void 0 : data.campaigns) || [],
|
|
385
385
|
columns: columns2,
|
|
386
|
-
getRowId: (
|
|
386
|
+
getRowId: (campaign) => campaign.id,
|
|
387
387
|
pagination: {
|
|
388
388
|
state: pagination,
|
|
389
389
|
onPaginationChange: setPagination
|
|
390
390
|
},
|
|
391
|
-
rowCount: (data == null ? void 0 : data.count)
|
|
391
|
+
rowCount: (data == null ? void 0 : data.count) || 0,
|
|
392
392
|
onRowClick: (_, row) => {
|
|
393
|
-
navigate(`/
|
|
393
|
+
navigate(`/flash-sales/${row.id}`);
|
|
394
394
|
}
|
|
395
395
|
});
|
|
396
396
|
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
397
397
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
398
|
-
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "
|
|
399
|
-
/* @__PURE__ */ jsx(Button, { onClick: () => navigate("/
|
|
398
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "Campaigns" }),
|
|
399
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => navigate("/flash-sales/create"), children: "Create Campaign" })
|
|
400
400
|
] }),
|
|
401
401
|
/* @__PURE__ */ jsxs(DataTable, { instance: table, children: [
|
|
402
402
|
/* @__PURE__ */ jsx(DataTable.Table, {}),
|
|
@@ -404,12 +404,12 @@ const CouponPage = () => {
|
|
|
404
404
|
] })
|
|
405
405
|
] });
|
|
406
406
|
};
|
|
407
|
-
const
|
|
408
|
-
return /* @__PURE__ */ jsx(
|
|
407
|
+
const FlashSale = () => {
|
|
408
|
+
return /* @__PURE__ */ jsx(FlashSalePage, {});
|
|
409
409
|
};
|
|
410
410
|
const config$2 = defineRouteConfig({
|
|
411
|
-
label: "
|
|
412
|
-
icon:
|
|
411
|
+
label: "Flash Sale",
|
|
412
|
+
icon: Sparkles
|
|
413
413
|
});
|
|
414
414
|
var isCheckBoxInput = (element) => element.type === "checkbox";
|
|
415
415
|
var isDateObject = (value) => value instanceof Date;
|
|
@@ -2292,134 +2292,64 @@ var n = function(e2, o2) {
|
|
|
2292
2292
|
}
|
|
2293
2293
|
};
|
|
2294
2294
|
};
|
|
2295
|
-
const
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2295
|
+
const couponSchema = z.object({
|
|
2296
|
+
name: z.string().min(1, "Name is required"),
|
|
2297
|
+
description: z.string().min(1, "Description is required"),
|
|
2298
|
+
type: z.literal("coupon"),
|
|
2299
|
+
code: z.string().min(1, "Coupon code is required"),
|
|
2300
|
+
discount_type: z.enum(["percentage", "fixed"]),
|
|
2301
|
+
discount_value: z.number().positive("Discount must be positive"),
|
|
2302
|
+
currency_code: z.string().optional(),
|
|
2303
|
+
starts_at: z.string().min(1, "Start date is required"),
|
|
2304
|
+
ends_at: z.string().min(1, "End date is required"),
|
|
2305
|
+
allocation: z.enum(["each", "total"]).optional(),
|
|
2306
|
+
target_type: z.enum(["order", "items"]).optional()
|
|
2307
|
+
}).superRefine((data, ctx) => {
|
|
2308
|
+
if (new Date(data.ends_at) < new Date(data.starts_at)) {
|
|
2309
|
+
ctx.addIssue({
|
|
2310
|
+
code: z.ZodIssueCode.custom,
|
|
2311
|
+
path: ["ends_at"],
|
|
2312
|
+
message: "End date must be after start date"
|
|
2313
|
+
});
|
|
2314
|
+
}
|
|
2315
|
+
if (data.discount_type === "fixed" && !data.currency_code) {
|
|
2316
|
+
ctx.addIssue({
|
|
2317
|
+
code: z.ZodIssueCode.custom,
|
|
2318
|
+
path: ["currency_code"],
|
|
2319
|
+
message: "Currency is required for fixed discount"
|
|
2320
|
+
});
|
|
2300
2321
|
}
|
|
2301
2322
|
});
|
|
2302
|
-
const
|
|
2303
|
-
const columns = [
|
|
2304
|
-
columnHelper.accessor("title", {
|
|
2305
|
-
header: "Title",
|
|
2306
|
-
cell: (info) => /* @__PURE__ */ jsx(Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
2307
|
-
}),
|
|
2308
|
-
columnHelper.accessor("description", {
|
|
2309
|
-
header: "Description",
|
|
2310
|
-
cell: (info) => /* @__PURE__ */ jsx(Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
2311
|
-
})
|
|
2312
|
-
];
|
|
2313
|
-
const ProductSelector = ({
|
|
2314
|
-
selectedProductIds,
|
|
2315
|
-
onSelectProduct
|
|
2316
|
-
}) => {
|
|
2317
|
-
const [search, setSearch] = useState("");
|
|
2318
|
-
const debouncedSearchHandler = debounce((value) => {
|
|
2319
|
-
setSearch(value);
|
|
2320
|
-
}, 500);
|
|
2321
|
-
const [products, setProducts] = useState(null);
|
|
2322
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
2323
|
-
useEffect(() => {
|
|
2324
|
-
const fetchProducts = async () => {
|
|
2325
|
-
setIsLoading(true);
|
|
2326
|
-
try {
|
|
2327
|
-
const result = await sdk.admin.product.list({
|
|
2328
|
-
q: search,
|
|
2329
|
-
limit: 100
|
|
2330
|
-
});
|
|
2331
|
-
setProducts(result);
|
|
2332
|
-
} catch (error) {
|
|
2333
|
-
console.error("Failed to fetch products:", error);
|
|
2334
|
-
} finally {
|
|
2335
|
-
setIsLoading(false);
|
|
2336
|
-
}
|
|
2337
|
-
};
|
|
2338
|
-
fetchProducts();
|
|
2339
|
-
}, [selectedProductIds, search]);
|
|
2340
|
-
const selectableProducts = useMemo(() => {
|
|
2341
|
-
var _a;
|
|
2342
|
-
return (_a = products == null ? void 0 : products.products) == null ? void 0 : _a.filter(
|
|
2343
|
-
(product) => !selectedProductIds.includes(product.id)
|
|
2344
|
-
);
|
|
2345
|
-
}, [products == null ? void 0 : products.products, selectedProductIds]);
|
|
2346
|
-
const table = useDataTable({
|
|
2347
|
-
data: selectableProducts || [],
|
|
2348
|
-
columns,
|
|
2349
|
-
getRowId: (product) => product.id,
|
|
2350
|
-
onRowClick: (_, row) => {
|
|
2351
|
-
onSelectProduct == null ? void 0 : onSelectProduct(row.original);
|
|
2352
|
-
}
|
|
2353
|
-
});
|
|
2354
|
-
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
2355
|
-
/* @__PURE__ */ jsx(
|
|
2356
|
-
Input,
|
|
2357
|
-
{
|
|
2358
|
-
className: "text-lg py-2",
|
|
2359
|
-
placeholder: "Search products...",
|
|
2360
|
-
onChange: (e2) => debouncedSearchHandler(e2.target.value)
|
|
2361
|
-
}
|
|
2362
|
-
),
|
|
2363
|
-
/* @__PURE__ */ jsx(DataTable, { instance: table, children: /* @__PURE__ */ jsx(DataTable.Table, {}) })
|
|
2364
|
-
] });
|
|
2365
|
-
};
|
|
2366
|
-
const customCampaignSchema = z.object({
|
|
2367
|
-
name: z.string().min(1, "Name is required"),
|
|
2368
|
-
description: z.string().min(1, "Description is required"),
|
|
2369
|
-
type: z.literal("flash-sale"),
|
|
2370
|
-
starts_at: z.string().min(1, "Start date is required"),
|
|
2371
|
-
ends_at: z.string().min(1, "End date is required")
|
|
2372
|
-
});
|
|
2373
|
-
const campaignProductSchema = z.object({
|
|
2374
|
-
product: z.object({
|
|
2375
|
-
id: z.string(),
|
|
2376
|
-
title: z.string()
|
|
2377
|
-
}),
|
|
2378
|
-
discountType: z.enum([
|
|
2379
|
-
"percentage"
|
|
2380
|
-
// , "fixed"
|
|
2381
|
-
]),
|
|
2382
|
-
discountValue: z.number().min(1),
|
|
2383
|
-
limit: z.number().min(1),
|
|
2384
|
-
maxQty: z.number().min(1)
|
|
2385
|
-
});
|
|
2386
|
-
const campaignDataSchema = customCampaignSchema.extend({
|
|
2387
|
-
products: z.array(campaignProductSchema).min(1, "At least one product is required")
|
|
2388
|
-
}).refine(
|
|
2389
|
-
(data) => new Date(data.starts_at) < new Date(data.ends_at),
|
|
2390
|
-
"End date must be after start date"
|
|
2391
|
-
);
|
|
2392
|
-
const FlashSaleForm = ({
|
|
2323
|
+
const CouponForm = ({
|
|
2393
2324
|
initialData,
|
|
2394
|
-
initialProducts,
|
|
2395
2325
|
onSubmit,
|
|
2396
2326
|
onCancel,
|
|
2397
2327
|
disabled = false
|
|
2398
2328
|
}) => {
|
|
2399
|
-
var _a;
|
|
2400
2329
|
const {
|
|
2401
|
-
control,
|
|
2402
2330
|
register,
|
|
2331
|
+
control,
|
|
2403
2332
|
handleSubmit,
|
|
2404
2333
|
watch,
|
|
2405
2334
|
setValue,
|
|
2406
2335
|
formState: { errors }
|
|
2407
2336
|
} = useForm({
|
|
2408
|
-
resolver: t(
|
|
2337
|
+
resolver: t(couponSchema),
|
|
2409
2338
|
defaultValues: {
|
|
2410
|
-
name: (initialData == null ? void 0 : initialData.name)
|
|
2411
|
-
description: (initialData == null ? void 0 : initialData.description)
|
|
2412
|
-
type: "
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2339
|
+
name: (initialData == null ? void 0 : initialData.name) ?? "",
|
|
2340
|
+
description: (initialData == null ? void 0 : initialData.description) ?? "",
|
|
2341
|
+
type: "coupon",
|
|
2342
|
+
code: (initialData == null ? void 0 : initialData.code) ?? "",
|
|
2343
|
+
discount_type: (initialData == null ? void 0 : initialData.discount_type) ?? "percentage",
|
|
2344
|
+
discount_value: (initialData == null ? void 0 : initialData.discount_value) ?? 0,
|
|
2345
|
+
currency_code: (initialData == null ? void 0 : initialData.currency_code) ?? "",
|
|
2346
|
+
starts_at: (initialData == null ? void 0 : initialData.starts_at) ?? dayjs().startOf("day").format("YYYY-MM-DDTHH:mm"),
|
|
2347
|
+
ends_at: (initialData == null ? void 0 : initialData.ends_at) ?? dayjs().endOf("day").format("YYYY-MM-DDTHH:mm"),
|
|
2348
|
+
allocation: (initialData == null ? void 0 : initialData.allocation) ?? "total",
|
|
2349
|
+
target_type: (initialData == null ? void 0 : initialData.target_type) ?? "order"
|
|
2416
2350
|
}
|
|
2417
2351
|
});
|
|
2418
|
-
const
|
|
2419
|
-
control,
|
|
2420
|
-
name: "products"
|
|
2421
|
-
});
|
|
2422
|
-
const [openProductModal, setOpenProductModal] = useState(false);
|
|
2352
|
+
const discountType = watch("discount_type");
|
|
2423
2353
|
const startsAt = watch("starts_at");
|
|
2424
2354
|
const endsAt = watch("ends_at");
|
|
2425
2355
|
const handleDateTimeChange = (field, type, value) => {
|
|
@@ -2436,42 +2366,34 @@ const FlashSaleForm = ({
|
|
|
2436
2366
|
onSubmit(data);
|
|
2437
2367
|
};
|
|
2438
2368
|
const onFormError = () => {
|
|
2439
|
-
const errorMessages = Object.
|
|
2440
|
-
|
|
2441
|
-
if (key === "products" && Array.isArray(value)) {
|
|
2442
|
-
return value.map(
|
|
2443
|
-
(item, index) => item ? `Product ${index + 1}: ${Object.values(item).join(", ")}` : ""
|
|
2444
|
-
).filter(Boolean).join(", ");
|
|
2445
|
-
}
|
|
2446
|
-
return (value == null ? void 0 : value.message) || ((_a2 = value == null ? void 0 : value.root) == null ? void 0 : _a2.message);
|
|
2447
|
-
}).filter(Boolean).join(", ");
|
|
2448
|
-
toast.error("Invalid data", {
|
|
2369
|
+
const errorMessages = Object.values(errors).map((err) => err == null ? void 0 : err.message).filter(Boolean).join(", ");
|
|
2370
|
+
toast.error("Invalid coupon data", {
|
|
2449
2371
|
description: errorMessages
|
|
2450
2372
|
});
|
|
2451
2373
|
};
|
|
2452
2374
|
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
2453
2375
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2454
|
-
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "
|
|
2376
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "Coupon" }),
|
|
2455
2377
|
/* @__PURE__ */ jsx(Button, { variant: "transparent", onClick: onCancel, children: "Cancel" })
|
|
2456
2378
|
] }),
|
|
2457
2379
|
/* @__PURE__ */ jsxs(
|
|
2458
2380
|
"form",
|
|
2459
2381
|
{
|
|
2460
2382
|
onSubmit: handleSubmit(onFormSubmit, onFormError),
|
|
2461
|
-
className: "space-y-
|
|
2383
|
+
className: "space-y-6 my-8",
|
|
2462
2384
|
children: [
|
|
2463
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2385
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2464
2386
|
/* @__PURE__ */ jsx(Label, { children: "Name" }),
|
|
2465
2387
|
/* @__PURE__ */ jsx(Input, { ...register("name"), disabled }),
|
|
2466
2388
|
errors.name && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.name.message })
|
|
2467
2389
|
] }),
|
|
2468
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2390
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2469
2391
|
/* @__PURE__ */ jsx(Label, { children: "Description" }),
|
|
2470
2392
|
/* @__PURE__ */ jsx(Textarea, { ...register("description"), disabled }),
|
|
2471
2393
|
errors.description && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
|
|
2472
2394
|
] }),
|
|
2473
2395
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2474
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2396
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2475
2397
|
/* @__PURE__ */ jsx(Label, { children: "Start Date" }),
|
|
2476
2398
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2477
2399
|
/* @__PURE__ */ jsx(
|
|
@@ -2491,13 +2413,13 @@ const FlashSaleForm = ({
|
|
|
2491
2413
|
value: startsAt ? dayjs(startsAt).format("HH:mm") : "",
|
|
2492
2414
|
onChange: (e2) => handleDateTimeChange("starts_at", "time", e2.target.value),
|
|
2493
2415
|
disabled,
|
|
2494
|
-
className: "
|
|
2416
|
+
className: "flex-1"
|
|
2495
2417
|
}
|
|
2496
2418
|
)
|
|
2497
2419
|
] }),
|
|
2498
2420
|
errors.starts_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
|
|
2499
2421
|
] }),
|
|
2500
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2422
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2501
2423
|
/* @__PURE__ */ jsx(Label, { children: "End Date" }),
|
|
2502
2424
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2503
2425
|
/* @__PURE__ */ jsx(
|
|
@@ -2517,1450 +2439,1493 @@ const FlashSaleForm = ({
|
|
|
2517
2439
|
value: endsAt ? dayjs(endsAt).format("HH:mm") : "",
|
|
2518
2440
|
onChange: (e2) => handleDateTimeChange("ends_at", "time", e2.target.value),
|
|
2519
2441
|
disabled,
|
|
2520
|
-
className: "
|
|
2442
|
+
className: "flex-1"
|
|
2521
2443
|
}
|
|
2522
2444
|
)
|
|
2523
2445
|
] }),
|
|
2524
2446
|
errors.ends_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
|
|
2525
2447
|
] })
|
|
2526
2448
|
] }),
|
|
2527
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
2528
|
-
/* @__PURE__ */
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
{
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
children: "Add Product"
|
|
2537
|
-
}
|
|
2538
|
-
)
|
|
2539
|
-
] }),
|
|
2540
|
-
((_a = errors.products) == null ? void 0 : _a.root) && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm", children: errors.products.root.message }),
|
|
2541
|
-
/* @__PURE__ */ jsxs(Table, { children: [
|
|
2542
|
-
/* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
2543
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Product" }),
|
|
2544
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Discount Type" }),
|
|
2545
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Discount Value" }),
|
|
2546
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Limit" }),
|
|
2547
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Max Qty per Order" }),
|
|
2548
|
-
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Actions" })
|
|
2549
|
-
] }) }),
|
|
2550
|
-
/* @__PURE__ */ jsx(Table.Body, { children: fields.map((field, index) => /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
2551
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: field.product.title }),
|
|
2552
|
-
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
2449
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2450
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2451
|
+
/* @__PURE__ */ jsx(Label, { children: "Coupon Code" }),
|
|
2452
|
+
/* @__PURE__ */ jsx(Input, { ...register("code"), disabled }),
|
|
2453
|
+
errors.code && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.code.message })
|
|
2454
|
+
] }),
|
|
2455
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2456
|
+
/* @__PURE__ */ jsx(Label, { children: "Discount Type" }),
|
|
2457
|
+
/* @__PURE__ */ jsx(
|
|
2553
2458
|
Controller,
|
|
2554
2459
|
{
|
|
2555
|
-
name:
|
|
2460
|
+
name: "discount_type",
|
|
2556
2461
|
control,
|
|
2557
|
-
render: ({ field
|
|
2462
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(
|
|
2558
2463
|
Select,
|
|
2559
2464
|
{
|
|
2560
|
-
value:
|
|
2561
|
-
onValueChange:
|
|
2465
|
+
value: field.value,
|
|
2466
|
+
onValueChange: field.onChange,
|
|
2562
2467
|
disabled,
|
|
2563
2468
|
children: [
|
|
2564
2469
|
/* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select discount type" }) }),
|
|
2565
|
-
/* @__PURE__ */
|
|
2470
|
+
/* @__PURE__ */ jsxs(Select.Content, { children: [
|
|
2471
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "percentage", children: "Percentage" }),
|
|
2472
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "fixed", children: "Fixed Amount" })
|
|
2473
|
+
] })
|
|
2566
2474
|
]
|
|
2567
2475
|
}
|
|
2568
2476
|
)
|
|
2569
2477
|
}
|
|
2570
|
-
)
|
|
2571
|
-
|
|
2478
|
+
)
|
|
2479
|
+
] })
|
|
2480
|
+
] }),
|
|
2481
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2482
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2483
|
+
/* @__PURE__ */ jsx(Label, { children: "Discount Value" }),
|
|
2484
|
+
/* @__PURE__ */ jsx(
|
|
2572
2485
|
Controller,
|
|
2573
2486
|
{
|
|
2574
|
-
name:
|
|
2487
|
+
name: "discount_value",
|
|
2575
2488
|
control,
|
|
2576
|
-
render: ({ field
|
|
2489
|
+
render: ({ field }) => /* @__PURE__ */ jsx(
|
|
2577
2490
|
Input,
|
|
2578
2491
|
{
|
|
2579
2492
|
type: "number",
|
|
2580
|
-
value:
|
|
2581
|
-
|
|
2493
|
+
value: field.value,
|
|
2494
|
+
min: 0,
|
|
2495
|
+
step: field.value % 1 === 0 ? 1 : 0.01,
|
|
2496
|
+
onChange: (e2) => field.onChange(Number(e2.target.value)),
|
|
2582
2497
|
disabled
|
|
2583
2498
|
}
|
|
2584
2499
|
)
|
|
2585
2500
|
}
|
|
2586
|
-
)
|
|
2587
|
-
/* @__PURE__ */ jsx(
|
|
2501
|
+
),
|
|
2502
|
+
errors.discount_value && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.discount_value.message })
|
|
2503
|
+
] }),
|
|
2504
|
+
discountType === "fixed" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2505
|
+
/* @__PURE__ */ jsx(Label, { children: "Currency Code" }),
|
|
2506
|
+
/* @__PURE__ */ jsx(Input, { ...register("currency_code"), disabled }),
|
|
2507
|
+
errors.currency_code && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.currency_code.message })
|
|
2508
|
+
] })
|
|
2509
|
+
] }),
|
|
2510
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2511
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2512
|
+
/* @__PURE__ */ jsx(Label, { children: "Allocation" }),
|
|
2513
|
+
/* @__PURE__ */ jsx(
|
|
2588
2514
|
Controller,
|
|
2589
2515
|
{
|
|
2590
|
-
name:
|
|
2516
|
+
name: "allocation",
|
|
2591
2517
|
control,
|
|
2592
|
-
render: ({ field
|
|
2593
|
-
|
|
2518
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(
|
|
2519
|
+
Select,
|
|
2594
2520
|
{
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2521
|
+
value: field.value,
|
|
2522
|
+
onValueChange: field.onChange,
|
|
2523
|
+
disabled,
|
|
2524
|
+
children: [
|
|
2525
|
+
/* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select allocation" }) }),
|
|
2526
|
+
/* @__PURE__ */ jsxs(Select.Content, { children: [
|
|
2527
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "total", children: "Order Total" }),
|
|
2528
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "each", children: "Each Item" })
|
|
2529
|
+
] })
|
|
2530
|
+
]
|
|
2599
2531
|
}
|
|
2600
2532
|
)
|
|
2601
2533
|
}
|
|
2602
|
-
)
|
|
2603
|
-
|
|
2534
|
+
)
|
|
2535
|
+
] }),
|
|
2536
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2537
|
+
/* @__PURE__ */ jsx(Label, { children: "Target Type" }),
|
|
2538
|
+
/* @__PURE__ */ jsx(
|
|
2604
2539
|
Controller,
|
|
2605
2540
|
{
|
|
2606
|
-
name:
|
|
2541
|
+
name: "target_type",
|
|
2607
2542
|
control,
|
|
2608
|
-
render: ({ field
|
|
2609
|
-
|
|
2543
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(
|
|
2544
|
+
Select,
|
|
2610
2545
|
{
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2546
|
+
value: field.value,
|
|
2547
|
+
onValueChange: field.onChange,
|
|
2548
|
+
disabled,
|
|
2549
|
+
children: [
|
|
2550
|
+
/* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select target" }) }),
|
|
2551
|
+
/* @__PURE__ */ jsxs(Select.Content, { children: [
|
|
2552
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "order", children: "Order" }),
|
|
2553
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "items", children: "Items" })
|
|
2554
|
+
] })
|
|
2555
|
+
]
|
|
2615
2556
|
}
|
|
2616
2557
|
)
|
|
2617
2558
|
}
|
|
2618
|
-
)
|
|
2619
|
-
|
|
2620
|
-
Button,
|
|
2621
|
-
{
|
|
2622
|
-
type: "button",
|
|
2623
|
-
variant: "danger",
|
|
2624
|
-
onClick: () => remove(index),
|
|
2625
|
-
disabled,
|
|
2626
|
-
children: /* @__PURE__ */ jsx(Trash, {})
|
|
2627
|
-
}
|
|
2628
|
-
) })
|
|
2629
|
-
] }, field.id)) })
|
|
2559
|
+
)
|
|
2560
|
+
] })
|
|
2630
2561
|
] }),
|
|
2631
|
-
/* @__PURE__ */
|
|
2562
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
2563
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", type: "button", onClick: onCancel, children: "Cancel" }),
|
|
2564
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", disabled, children: "Save Coupon" })
|
|
2565
|
+
] })
|
|
2632
2566
|
]
|
|
2633
2567
|
}
|
|
2634
|
-
)
|
|
2635
|
-
/* @__PURE__ */ jsx(FocusModal, { open: openProductModal, onOpenChange: setOpenProductModal, children: /* @__PURE__ */ jsxs(FocusModal.Content, { children: [
|
|
2636
|
-
/* @__PURE__ */ jsx(FocusModal.Header, { title: "Add Product" }),
|
|
2637
|
-
/* @__PURE__ */ jsx(FocusModal.Body, { children: /* @__PURE__ */ jsx(
|
|
2638
|
-
ProductSelector,
|
|
2639
|
-
{
|
|
2640
|
-
selectedProductIds: fields.map((f2) => f2.product.id),
|
|
2641
|
-
onSelectProduct: (product) => {
|
|
2642
|
-
append({
|
|
2643
|
-
product,
|
|
2644
|
-
discountType: "percentage",
|
|
2645
|
-
discountValue: 10,
|
|
2646
|
-
limit: 10,
|
|
2647
|
-
maxQty: 1
|
|
2648
|
-
});
|
|
2649
|
-
setOpenProductModal(false);
|
|
2650
|
-
}
|
|
2651
|
-
}
|
|
2652
|
-
) })
|
|
2653
|
-
] }) })
|
|
2568
|
+
)
|
|
2654
2569
|
] });
|
|
2655
2570
|
};
|
|
2656
|
-
const
|
|
2657
|
-
const
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
starts_at: new Date(campaignData.starts_at).toUTCString(),
|
|
2664
|
-
ends_at: new Date(campaignData.ends_at).toUTCString()
|
|
2665
|
-
});
|
|
2666
|
-
toast.success("Flash sale created successfully");
|
|
2667
|
-
navigate("/flash-sales");
|
|
2668
|
-
} catch (error) {
|
|
2669
|
-
let message;
|
|
2670
|
-
if (axios.isAxiosError(error)) {
|
|
2671
|
-
console.log(error);
|
|
2672
|
-
message = (_a = error.response) == null ? void 0 : _a.data.message;
|
|
2673
|
-
} else if (error instanceof Error) {
|
|
2674
|
-
message = error.message;
|
|
2675
|
-
} else {
|
|
2676
|
-
message = "Failed to create flash sale";
|
|
2677
|
-
}
|
|
2678
|
-
toast.error("Failed to create flash sale", {
|
|
2679
|
-
description: message
|
|
2680
|
-
});
|
|
2571
|
+
const useCouponById = (id) => {
|
|
2572
|
+
const [data, setData] = useState(null);
|
|
2573
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
2574
|
+
const [error, setError] = useState(null);
|
|
2575
|
+
const fetchCoupon = async () => {
|
|
2576
|
+
if (!id) {
|
|
2577
|
+
return;
|
|
2681
2578
|
}
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2579
|
+
setIsLoading(true);
|
|
2580
|
+
setError(null);
|
|
2581
|
+
try {
|
|
2582
|
+
const response = await axios.get(`/admin/coupons/${id}`);
|
|
2583
|
+
setData(response.data);
|
|
2584
|
+
} catch (err) {
|
|
2585
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch coupon"));
|
|
2586
|
+
} finally {
|
|
2587
|
+
setIsLoading(false);
|
|
2688
2588
|
}
|
|
2689
|
-
|
|
2589
|
+
};
|
|
2590
|
+
useEffect(() => {
|
|
2591
|
+
fetchCoupon();
|
|
2592
|
+
}, [id]);
|
|
2593
|
+
return {
|
|
2594
|
+
data,
|
|
2595
|
+
isLoading,
|
|
2596
|
+
error,
|
|
2597
|
+
refetch: fetchCoupon
|
|
2598
|
+
};
|
|
2690
2599
|
};
|
|
2691
|
-
const
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
starts_at: z.string().min(1, "Start date is required"),
|
|
2700
|
-
ends_at: z.string().min(1, "End date is required"),
|
|
2701
|
-
allocation: z.enum(["each", "total"]).optional(),
|
|
2702
|
-
target_type: z.enum(["order", "items"]).optional()
|
|
2703
|
-
}).superRefine((data, ctx) => {
|
|
2704
|
-
if (new Date(data.ends_at) < new Date(data.starts_at)) {
|
|
2705
|
-
ctx.addIssue({
|
|
2706
|
-
code: z.ZodIssueCode.custom,
|
|
2707
|
-
path: ["ends_at"],
|
|
2708
|
-
message: "End date must be after start date"
|
|
2709
|
-
});
|
|
2710
|
-
}
|
|
2711
|
-
if (data.discount_type === "fixed" && !data.currency_code) {
|
|
2712
|
-
ctx.addIssue({
|
|
2713
|
-
code: z.ZodIssueCode.custom,
|
|
2714
|
-
path: ["currency_code"],
|
|
2715
|
-
message: "Currency is required for fixed discount"
|
|
2716
|
-
});
|
|
2717
|
-
}
|
|
2718
|
-
});
|
|
2719
|
-
const CouponForm = ({
|
|
2720
|
-
initialData,
|
|
2721
|
-
onSubmit,
|
|
2722
|
-
onCancel,
|
|
2723
|
-
disabled = false
|
|
2600
|
+
const MarkdownEditor = ({
|
|
2601
|
+
label,
|
|
2602
|
+
value,
|
|
2603
|
+
onChange,
|
|
2604
|
+
placeholder,
|
|
2605
|
+
helpText,
|
|
2606
|
+
rows = 10,
|
|
2607
|
+
showPreview = true
|
|
2724
2608
|
}) => {
|
|
2725
|
-
const
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
ends_at: (initialData == null ? void 0 : initialData.ends_at) ?? dayjs().endOf("day").format("YYYY-MM-DDTHH:mm"),
|
|
2744
|
-
allocation: (initialData == null ? void 0 : initialData.allocation) ?? "total",
|
|
2745
|
-
target_type: (initialData == null ? void 0 : initialData.target_type) ?? "order"
|
|
2746
|
-
}
|
|
2747
|
-
});
|
|
2748
|
-
const discountType = watch("discount_type");
|
|
2749
|
-
const startsAt = watch("starts_at");
|
|
2750
|
-
const endsAt = watch("ends_at");
|
|
2751
|
-
const handleDateTimeChange = (field, type, value) => {
|
|
2752
|
-
const currentValue = watch(field);
|
|
2753
|
-
if (type === "date") {
|
|
2754
|
-
const time = currentValue ? dayjs(currentValue).format("HH:mm") : field === "starts_at" ? "00:00" : "23:59";
|
|
2755
|
-
setValue(field, `${value}T${time}`);
|
|
2756
|
-
} else {
|
|
2757
|
-
const date = currentValue ? dayjs(currentValue).format("YYYY-MM-DD") : dayjs().format("YYYY-MM-DD");
|
|
2758
|
-
setValue(field, `${date}T${value}`);
|
|
2759
|
-
}
|
|
2760
|
-
};
|
|
2761
|
-
const onFormSubmit = (data) => {
|
|
2762
|
-
onSubmit(data);
|
|
2609
|
+
const [activeTab, setActiveTab] = useState("write");
|
|
2610
|
+
const insertMarkdown = (before, after = "") => {
|
|
2611
|
+
const textarea = document.getElementById(
|
|
2612
|
+
`markdown-${label}`
|
|
2613
|
+
);
|
|
2614
|
+
if (!textarea) return;
|
|
2615
|
+
const start = textarea.selectionStart;
|
|
2616
|
+
const end = textarea.selectionEnd;
|
|
2617
|
+
const selectedText = value.substring(start, end);
|
|
2618
|
+
const newText = value.substring(0, start) + before + selectedText + after + value.substring(end);
|
|
2619
|
+
onChange(newText);
|
|
2620
|
+
setTimeout(() => {
|
|
2621
|
+
textarea.focus();
|
|
2622
|
+
textarea.setSelectionRange(
|
|
2623
|
+
start + before.length,
|
|
2624
|
+
start + before.length + selectedText.length
|
|
2625
|
+
);
|
|
2626
|
+
}, 0);
|
|
2763
2627
|
};
|
|
2764
|
-
const
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2628
|
+
const renderMarkdownPreview = (markdown) => {
|
|
2629
|
+
let html = markdown;
|
|
2630
|
+
html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
|
|
2631
|
+
html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
|
|
2632
|
+
html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
|
|
2633
|
+
html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
2634
|
+
html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
|
|
2635
|
+
html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
|
|
2636
|
+
html = html.replace(/_(.+?)_/g, "<em>$1</em>");
|
|
2637
|
+
html = html.replace(
|
|
2638
|
+
/```(\w+)?\n([\s\S]+?)```/g,
|
|
2639
|
+
'<pre class="bg-ui-bg-subtle p-4 rounded-lg overflow-x-auto"><code class="language-$1">$2</code></pre>'
|
|
2640
|
+
);
|
|
2641
|
+
html = html.replace(
|
|
2642
|
+
/`([^`]+)`/g,
|
|
2643
|
+
'<code class="bg-ui-bg-subtle px-1 py-0.5 rounded text-sm">$1</code>'
|
|
2644
|
+
);
|
|
2645
|
+
html = html.replace(
|
|
2646
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
2647
|
+
'<a href="$2" class="text-ui-fg-interactive underline">$1</a>'
|
|
2648
|
+
);
|
|
2649
|
+
html = html.replace(
|
|
2650
|
+
/!\[([^\]]*)\]\(([^)]+)\)/g,
|
|
2651
|
+
'<img src="$2" alt="$1" class="max-w-full h-auto rounded-lg my-2" />'
|
|
2652
|
+
);
|
|
2653
|
+
html = html.replace(/^\* (.+)$/gim, "<li>$1</li>");
|
|
2654
|
+
html = html.replace(/^\- (.+)$/gim, "<li>$1</li>");
|
|
2655
|
+
html = html.replace(
|
|
2656
|
+
/(<li>.*<\/li>)/s,
|
|
2657
|
+
"<ul class='list-disc ml-6'>$1</ul>"
|
|
2658
|
+
);
|
|
2659
|
+
html = html.replace(/\n\n/g, "</p><p>");
|
|
2660
|
+
html = `<p>${html}</p>`;
|
|
2661
|
+
return html;
|
|
2769
2662
|
};
|
|
2770
|
-
return /* @__PURE__ */ jsxs(
|
|
2663
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2771
2664
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2772
|
-
/* @__PURE__ */ jsx(
|
|
2773
|
-
/* @__PURE__ */
|
|
2665
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `markdown-${label}`, className: "font-medium", children: label }),
|
|
2666
|
+
helpText && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-ui-fg-subtle", children: [
|
|
2667
|
+
/* @__PURE__ */ jsx(InformationCircle, { className: "h-4 w-4" }),
|
|
2668
|
+
/* @__PURE__ */ jsx("span", { children: helpText })
|
|
2669
|
+
] })
|
|
2774
2670
|
] }),
|
|
2775
|
-
/* @__PURE__ */ jsxs(
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
)
|
|
2671
|
+
showPreview ? /* @__PURE__ */ jsxs(Tabs, { value: activeTab, onValueChange: (v) => setActiveTab(v), children: [
|
|
2672
|
+
/* @__PURE__ */ jsxs(Tabs.List, { children: [
|
|
2673
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "write", children: "Write" }),
|
|
2674
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "preview", children: "Preview" })
|
|
2675
|
+
] }),
|
|
2676
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "write", className: "mt-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2677
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
|
|
2678
|
+
/* @__PURE__ */ jsx(
|
|
2679
|
+
Button,
|
|
2680
|
+
{
|
|
2681
|
+
type: "button",
|
|
2682
|
+
variant: "secondary",
|
|
2683
|
+
size: "small",
|
|
2684
|
+
onClick: () => insertMarkdown("**", "**"),
|
|
2685
|
+
title: "Bold",
|
|
2686
|
+
children: /* @__PURE__ */ jsx("strong", { children: "B" })
|
|
2687
|
+
}
|
|
2688
|
+
),
|
|
2689
|
+
/* @__PURE__ */ jsx(
|
|
2690
|
+
Button,
|
|
2691
|
+
{
|
|
2692
|
+
type: "button",
|
|
2693
|
+
variant: "secondary",
|
|
2694
|
+
size: "small",
|
|
2695
|
+
onClick: () => insertMarkdown("*", "*"),
|
|
2696
|
+
title: "Italic",
|
|
2697
|
+
children: /* @__PURE__ */ jsx("em", { children: "I" })
|
|
2698
|
+
}
|
|
2699
|
+
),
|
|
2700
|
+
/* @__PURE__ */ jsx(
|
|
2701
|
+
Button,
|
|
2702
|
+
{
|
|
2703
|
+
type: "button",
|
|
2704
|
+
variant: "secondary",
|
|
2705
|
+
size: "small",
|
|
2706
|
+
onClick: () => insertMarkdown("`", "`"),
|
|
2707
|
+
title: "Inline Code",
|
|
2708
|
+
children: /* @__PURE__ */ jsx(CommandLine, { className: "h-4 w-4" })
|
|
2709
|
+
}
|
|
2710
|
+
),
|
|
2711
|
+
/* @__PURE__ */ jsx(
|
|
2712
|
+
Button,
|
|
2713
|
+
{
|
|
2714
|
+
type: "button",
|
|
2715
|
+
variant: "secondary",
|
|
2716
|
+
size: "small",
|
|
2717
|
+
onClick: () => insertMarkdown("## "),
|
|
2718
|
+
title: "Heading",
|
|
2719
|
+
children: "H2"
|
|
2720
|
+
}
|
|
2721
|
+
),
|
|
2722
|
+
/* @__PURE__ */ jsx(
|
|
2723
|
+
Button,
|
|
2724
|
+
{
|
|
2725
|
+
type: "button",
|
|
2726
|
+
variant: "secondary",
|
|
2727
|
+
size: "small",
|
|
2728
|
+
onClick: () => insertMarkdown("### "),
|
|
2729
|
+
title: "Heading",
|
|
2730
|
+
children: "H3"
|
|
2731
|
+
}
|
|
2732
|
+
),
|
|
2733
|
+
/* @__PURE__ */ jsx(
|
|
2734
|
+
Button,
|
|
2735
|
+
{
|
|
2736
|
+
type: "button",
|
|
2737
|
+
variant: "secondary",
|
|
2738
|
+
size: "small",
|
|
2739
|
+
onClick: () => insertMarkdown("[", "](url)"),
|
|
2740
|
+
title: "Link",
|
|
2741
|
+
children: "Link"
|
|
2742
|
+
}
|
|
2743
|
+
),
|
|
2744
|
+
/* @__PURE__ */ jsx(
|
|
2745
|
+
Button,
|
|
2746
|
+
{
|
|
2747
|
+
type: "button",
|
|
2748
|
+
variant: "secondary",
|
|
2749
|
+
size: "small",
|
|
2750
|
+
onClick: () => insertMarkdown(""),
|
|
2751
|
+
title: "Image",
|
|
2752
|
+
children: "Image"
|
|
2753
|
+
}
|
|
2754
|
+
),
|
|
2755
|
+
/* @__PURE__ */ jsx(
|
|
2756
|
+
Button,
|
|
2757
|
+
{
|
|
2758
|
+
type: "button",
|
|
2759
|
+
variant: "secondary",
|
|
2760
|
+
size: "small",
|
|
2761
|
+
onClick: () => insertMarkdown("```\n", "\n```"),
|
|
2762
|
+
title: "Code Block",
|
|
2763
|
+
children: "Code"
|
|
2764
|
+
}
|
|
2765
|
+
),
|
|
2766
|
+
/* @__PURE__ */ jsx(
|
|
2767
|
+
Button,
|
|
2768
|
+
{
|
|
2769
|
+
type: "button",
|
|
2770
|
+
variant: "secondary",
|
|
2771
|
+
size: "small",
|
|
2772
|
+
onClick: () => insertMarkdown("- "),
|
|
2773
|
+
title: "List",
|
|
2774
|
+
children: "List"
|
|
2775
|
+
}
|
|
2776
|
+
)
|
|
2777
|
+
] }),
|
|
2778
|
+
/* @__PURE__ */ jsx(
|
|
2779
|
+
Textarea,
|
|
2780
|
+
{
|
|
2781
|
+
id: `markdown-${label}`,
|
|
2782
|
+
value,
|
|
2783
|
+
onChange: (e2) => onChange(e2.target.value),
|
|
2784
|
+
placeholder,
|
|
2785
|
+
rows,
|
|
2786
|
+
className: "font-mono text-sm"
|
|
2787
|
+
}
|
|
2788
|
+
)
|
|
2789
|
+
] }) }),
|
|
2790
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "preview", className: "mt-4", children: /* @__PURE__ */ jsx(
|
|
2791
|
+
"div",
|
|
2792
|
+
{
|
|
2793
|
+
className: clx(
|
|
2794
|
+
"min-h-[200px] rounded-lg border border-ui-border-base bg-ui-bg-subtle p-4",
|
|
2795
|
+
"prose prose-sm max-w-none"
|
|
2796
|
+
),
|
|
2797
|
+
dangerouslySetInnerHTML: {
|
|
2798
|
+
__html: value ? renderMarkdownPreview(value) : '<p class="text-ui-fg-subtle">Nothing to preview</p>'
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
) })
|
|
2802
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2803
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
|
|
2804
|
+
/* @__PURE__ */ jsx(
|
|
2805
|
+
Button,
|
|
2806
|
+
{
|
|
2807
|
+
type: "button",
|
|
2808
|
+
variant: "secondary",
|
|
2809
|
+
size: "small",
|
|
2810
|
+
onClick: () => insertMarkdown("**", "**"),
|
|
2811
|
+
title: "Bold",
|
|
2812
|
+
children: /* @__PURE__ */ jsx("strong", { children: "B" })
|
|
2813
|
+
}
|
|
2814
|
+
),
|
|
2815
|
+
/* @__PURE__ */ jsx(
|
|
2816
|
+
Button,
|
|
2817
|
+
{
|
|
2818
|
+
type: "button",
|
|
2819
|
+
variant: "secondary",
|
|
2820
|
+
size: "small",
|
|
2821
|
+
onClick: () => insertMarkdown("*", "*"),
|
|
2822
|
+
title: "Italic",
|
|
2823
|
+
children: /* @__PURE__ */ jsx("em", { children: "I" })
|
|
2824
|
+
}
|
|
2825
|
+
),
|
|
2826
|
+
/* @__PURE__ */ jsx(
|
|
2827
|
+
Button,
|
|
2828
|
+
{
|
|
2829
|
+
type: "button",
|
|
2830
|
+
variant: "secondary",
|
|
2831
|
+
size: "small",
|
|
2832
|
+
onClick: () => insertMarkdown("`", "`"),
|
|
2833
|
+
title: "Inline Code",
|
|
2834
|
+
children: /* @__PURE__ */ jsx(CommandLine, { className: "h-4 w-4" })
|
|
2835
|
+
}
|
|
2836
|
+
),
|
|
2837
|
+
/* @__PURE__ */ jsx(
|
|
2838
|
+
Button,
|
|
2839
|
+
{
|
|
2840
|
+
type: "button",
|
|
2841
|
+
variant: "secondary",
|
|
2842
|
+
size: "small",
|
|
2843
|
+
onClick: () => insertMarkdown("```\n", "\n```"),
|
|
2844
|
+
title: "Code Block",
|
|
2845
|
+
children: "Code"
|
|
2846
|
+
}
|
|
2847
|
+
)
|
|
2848
|
+
] }),
|
|
2849
|
+
/* @__PURE__ */ jsx(
|
|
2850
|
+
Textarea,
|
|
2851
|
+
{
|
|
2852
|
+
id: `markdown-${label}`,
|
|
2853
|
+
value,
|
|
2854
|
+
onChange: (e2) => onChange(e2.target.value),
|
|
2855
|
+
placeholder,
|
|
2856
|
+
rows,
|
|
2857
|
+
className: "font-mono text-sm"
|
|
2858
|
+
}
|
|
2859
|
+
)
|
|
2860
|
+
] }),
|
|
2861
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-ui-fg-subtle", children: "Supports Markdown formatting: **bold**, *italic*, `code`, ```code blocks```, [links](url), , and lists" })
|
|
2965
2862
|
] });
|
|
2966
2863
|
};
|
|
2967
|
-
const
|
|
2968
|
-
|
|
2969
|
-
|
|
2864
|
+
const CampaignImageUploader = ({
|
|
2865
|
+
campaignId,
|
|
2866
|
+
imageType,
|
|
2867
|
+
currentImageUrl,
|
|
2868
|
+
onClose,
|
|
2869
|
+
onSuccess
|
|
2870
|
+
}) => {
|
|
2871
|
+
const [displayImage, setDisplayImage] = useState(currentImageUrl || null);
|
|
2872
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
2873
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
2970
2874
|
const [error, setError] = useState(null);
|
|
2971
|
-
const
|
|
2972
|
-
|
|
2973
|
-
|
|
2875
|
+
const [previewUrl, setPreviewUrl] = useState(null);
|
|
2876
|
+
const isThumbnail = imageType === "thumbnail";
|
|
2877
|
+
const maxSize = 5;
|
|
2878
|
+
const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
2879
|
+
const formatFileTypes = () => {
|
|
2880
|
+
return "JPEG, PNG, GIF, or WebP";
|
|
2881
|
+
};
|
|
2882
|
+
const validateFile = (file) => {
|
|
2883
|
+
if (!allowedTypes.includes(file.type)) {
|
|
2884
|
+
setError(`Please upload a valid image file (${formatFileTypes()})`);
|
|
2885
|
+
return false;
|
|
2974
2886
|
}
|
|
2975
|
-
|
|
2887
|
+
if (file.size > maxSize * 1024 * 1024) {
|
|
2888
|
+
setError(`File size must be less than ${maxSize}MB`);
|
|
2889
|
+
return false;
|
|
2890
|
+
}
|
|
2891
|
+
return true;
|
|
2892
|
+
};
|
|
2893
|
+
const handleFileUpload = async (file) => {
|
|
2894
|
+
var _a, _b;
|
|
2895
|
+
if (!validateFile(file)) return;
|
|
2976
2896
|
setError(null);
|
|
2897
|
+
const reader = new FileReader();
|
|
2898
|
+
reader.onloadend = () => {
|
|
2899
|
+
setPreviewUrl(reader.result);
|
|
2900
|
+
};
|
|
2901
|
+
reader.readAsDataURL(file);
|
|
2902
|
+
setIsUploading(true);
|
|
2903
|
+
const formData = new FormData();
|
|
2904
|
+
formData.append("file", file);
|
|
2977
2905
|
try {
|
|
2978
|
-
const response = await
|
|
2979
|
-
|
|
2906
|
+
const response = await fetch(
|
|
2907
|
+
`/admin/campaigns/${campaignId}/${imageType}`,
|
|
2908
|
+
{
|
|
2909
|
+
method: "POST",
|
|
2910
|
+
body: formData,
|
|
2911
|
+
credentials: "include"
|
|
2912
|
+
}
|
|
2913
|
+
);
|
|
2914
|
+
if (response.ok) {
|
|
2915
|
+
const result = await response.json();
|
|
2916
|
+
const newImageUrl = imageType === "image" ? (_a = result.campaign_detail) == null ? void 0 : _a.image_url : (_b = result.campaign_detail) == null ? void 0 : _b.thumbnail_url;
|
|
2917
|
+
setDisplayImage(newImageUrl);
|
|
2918
|
+
setPreviewUrl(null);
|
|
2919
|
+
onSuccess == null ? void 0 : onSuccess();
|
|
2920
|
+
setTimeout(() => {
|
|
2921
|
+
onClose();
|
|
2922
|
+
}, 1e3);
|
|
2923
|
+
} else {
|
|
2924
|
+
const errorData = await response.json();
|
|
2925
|
+
setError(errorData.error || `Failed to upload ${imageType}`);
|
|
2926
|
+
setPreviewUrl(null);
|
|
2927
|
+
}
|
|
2980
2928
|
} catch (err) {
|
|
2981
|
-
setError(
|
|
2929
|
+
setError(`Error uploading ${imageType}`);
|
|
2930
|
+
setPreviewUrl(null);
|
|
2931
|
+
console.error(`Error uploading ${imageType}:`, err);
|
|
2982
2932
|
} finally {
|
|
2983
|
-
|
|
2933
|
+
setIsUploading(false);
|
|
2984
2934
|
}
|
|
2985
2935
|
};
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
label,
|
|
2998
|
-
value,
|
|
2999
|
-
onChange,
|
|
3000
|
-
placeholder,
|
|
3001
|
-
helpText,
|
|
3002
|
-
rows = 10,
|
|
3003
|
-
showPreview = true
|
|
3004
|
-
}) => {
|
|
3005
|
-
const [activeTab, setActiveTab] = useState("write");
|
|
3006
|
-
const insertMarkdown = (before, after = "") => {
|
|
3007
|
-
const textarea = document.getElementById(
|
|
3008
|
-
`markdown-${label}`
|
|
3009
|
-
);
|
|
3010
|
-
if (!textarea) return;
|
|
3011
|
-
const start = textarea.selectionStart;
|
|
3012
|
-
const end = textarea.selectionEnd;
|
|
3013
|
-
const selectedText = value.substring(start, end);
|
|
3014
|
-
const newText = value.substring(0, start) + before + selectedText + after + value.substring(end);
|
|
3015
|
-
onChange(newText);
|
|
3016
|
-
setTimeout(() => {
|
|
3017
|
-
textarea.focus();
|
|
3018
|
-
textarea.setSelectionRange(
|
|
3019
|
-
start + before.length,
|
|
3020
|
-
start + before.length + selectedText.length
|
|
2936
|
+
const handleDelete = async () => {
|
|
2937
|
+
if (!confirm(`Are you sure you want to delete the ${imageType}?`)) return;
|
|
2938
|
+
setIsUploading(true);
|
|
2939
|
+
setError(null);
|
|
2940
|
+
try {
|
|
2941
|
+
const response = await fetch(
|
|
2942
|
+
`/admin/campaigns/${campaignId}/${imageType}`,
|
|
2943
|
+
{
|
|
2944
|
+
method: "DELETE",
|
|
2945
|
+
credentials: "include"
|
|
2946
|
+
}
|
|
3021
2947
|
);
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
);
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
2948
|
+
if (response.ok) {
|
|
2949
|
+
setDisplayImage(null);
|
|
2950
|
+
setPreviewUrl(null);
|
|
2951
|
+
onSuccess == null ? void 0 : onSuccess();
|
|
2952
|
+
setTimeout(() => {
|
|
2953
|
+
onClose();
|
|
2954
|
+
}, 500);
|
|
2955
|
+
} else {
|
|
2956
|
+
const errorData = await response.json();
|
|
2957
|
+
setError(errorData.error || `Failed to delete ${imageType}`);
|
|
2958
|
+
}
|
|
2959
|
+
} catch (err) {
|
|
2960
|
+
setError(`Error deleting ${imageType}`);
|
|
2961
|
+
console.error(`Error deleting ${imageType}:`, err);
|
|
2962
|
+
} finally {
|
|
2963
|
+
setIsUploading(false);
|
|
2964
|
+
}
|
|
2965
|
+
};
|
|
2966
|
+
const handleDragEnter = useCallback((e2) => {
|
|
2967
|
+
e2.preventDefault();
|
|
2968
|
+
e2.stopPropagation();
|
|
2969
|
+
setIsDragging(true);
|
|
2970
|
+
}, []);
|
|
2971
|
+
const handleDragLeave = useCallback((e2) => {
|
|
2972
|
+
e2.preventDefault();
|
|
2973
|
+
e2.stopPropagation();
|
|
2974
|
+
setIsDragging(false);
|
|
2975
|
+
}, []);
|
|
2976
|
+
const handleDragOver = useCallback((e2) => {
|
|
2977
|
+
e2.preventDefault();
|
|
2978
|
+
e2.stopPropagation();
|
|
2979
|
+
}, []);
|
|
2980
|
+
const handleDrop = useCallback((e2) => {
|
|
2981
|
+
var _a;
|
|
2982
|
+
e2.preventDefault();
|
|
2983
|
+
e2.stopPropagation();
|
|
2984
|
+
setIsDragging(false);
|
|
2985
|
+
const file = (_a = e2.dataTransfer.files) == null ? void 0 : _a[0];
|
|
2986
|
+
if (file) {
|
|
2987
|
+
handleFileUpload(file);
|
|
2988
|
+
}
|
|
2989
|
+
}, []);
|
|
2990
|
+
const imageToDisplay = previewUrl || displayImage;
|
|
2991
|
+
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-2xl rounded-lg bg-ui-bg-base p-6", children: [
|
|
2992
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
2993
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2994
|
+
/* @__PURE__ */ jsxs(Heading, { level: "h2", children: [
|
|
2995
|
+
"Upload Campaign ",
|
|
2996
|
+
isThumbnail ? "Thumbnail" : "Image"
|
|
2997
|
+
] }),
|
|
2998
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: isThumbnail ? "Thumbnail for campaign listing" : "Main campaign image" })
|
|
3071
2999
|
] }),
|
|
3072
|
-
/* @__PURE__ */ jsx(
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
variant: "secondary",
|
|
3079
|
-
size: "small",
|
|
3080
|
-
onClick: () => insertMarkdown("**", "**"),
|
|
3081
|
-
title: "Bold",
|
|
3082
|
-
children: /* @__PURE__ */ jsx("strong", { children: "B" })
|
|
3083
|
-
}
|
|
3084
|
-
),
|
|
3085
|
-
/* @__PURE__ */ jsx(
|
|
3086
|
-
Button,
|
|
3087
|
-
{
|
|
3088
|
-
type: "button",
|
|
3089
|
-
variant: "secondary",
|
|
3090
|
-
size: "small",
|
|
3091
|
-
onClick: () => insertMarkdown("*", "*"),
|
|
3092
|
-
title: "Italic",
|
|
3093
|
-
children: /* @__PURE__ */ jsx("em", { children: "I" })
|
|
3094
|
-
}
|
|
3095
|
-
),
|
|
3096
|
-
/* @__PURE__ */ jsx(
|
|
3097
|
-
Button,
|
|
3098
|
-
{
|
|
3099
|
-
type: "button",
|
|
3100
|
-
variant: "secondary",
|
|
3101
|
-
size: "small",
|
|
3102
|
-
onClick: () => insertMarkdown("`", "`"),
|
|
3103
|
-
title: "Inline Code",
|
|
3104
|
-
children: /* @__PURE__ */ jsx(CommandLine, { className: "h-4 w-4" })
|
|
3105
|
-
}
|
|
3106
|
-
),
|
|
3107
|
-
/* @__PURE__ */ jsx(
|
|
3108
|
-
Button,
|
|
3109
|
-
{
|
|
3110
|
-
type: "button",
|
|
3111
|
-
variant: "secondary",
|
|
3112
|
-
size: "small",
|
|
3113
|
-
onClick: () => insertMarkdown("## "),
|
|
3114
|
-
title: "Heading",
|
|
3115
|
-
children: "H2"
|
|
3116
|
-
}
|
|
3117
|
-
),
|
|
3118
|
-
/* @__PURE__ */ jsx(
|
|
3119
|
-
Button,
|
|
3120
|
-
{
|
|
3121
|
-
type: "button",
|
|
3122
|
-
variant: "secondary",
|
|
3123
|
-
size: "small",
|
|
3124
|
-
onClick: () => insertMarkdown("### "),
|
|
3125
|
-
title: "Heading",
|
|
3126
|
-
children: "H3"
|
|
3127
|
-
}
|
|
3128
|
-
),
|
|
3129
|
-
/* @__PURE__ */ jsx(
|
|
3130
|
-
Button,
|
|
3131
|
-
{
|
|
3132
|
-
type: "button",
|
|
3133
|
-
variant: "secondary",
|
|
3134
|
-
size: "small",
|
|
3135
|
-
onClick: () => insertMarkdown("[", "](url)"),
|
|
3136
|
-
title: "Link",
|
|
3137
|
-
children: "Link"
|
|
3138
|
-
}
|
|
3139
|
-
),
|
|
3000
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsx(X, {}) })
|
|
3001
|
+
] }),
|
|
3002
|
+
error && /* @__PURE__ */ jsx(Alert, { variant: "error", dismissible: true, className: "mb-4", children: error }),
|
|
3003
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3004
|
+
imageToDisplay ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3005
|
+
/* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-subtle", children: [
|
|
3140
3006
|
/* @__PURE__ */ jsx(
|
|
3141
|
-
|
|
3007
|
+
"img",
|
|
3142
3008
|
{
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3009
|
+
src: imageToDisplay,
|
|
3010
|
+
alt: `Campaign ${imageType}`,
|
|
3011
|
+
className: clx(
|
|
3012
|
+
"w-full object-contain",
|
|
3013
|
+
isThumbnail ? "h-32" : "h-64"
|
|
3014
|
+
)
|
|
3149
3015
|
}
|
|
3150
3016
|
),
|
|
3151
|
-
/* @__PURE__ */ jsx(
|
|
3017
|
+
isUploading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsx("div", { className: "text-white", children: "Uploading..." }) })
|
|
3018
|
+
] }),
|
|
3019
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3020
|
+
/* @__PURE__ */ jsxs(
|
|
3152
3021
|
Button,
|
|
3153
3022
|
{
|
|
3154
|
-
type: "button",
|
|
3155
3023
|
variant: "secondary",
|
|
3156
|
-
|
|
3157
|
-
onClick: () =>
|
|
3158
|
-
|
|
3159
|
-
|
|
3024
|
+
disabled: isUploading,
|
|
3025
|
+
onClick: () => {
|
|
3026
|
+
var _a;
|
|
3027
|
+
return (_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click();
|
|
3028
|
+
},
|
|
3029
|
+
children: [
|
|
3030
|
+
/* @__PURE__ */ jsx(CloudArrowUp, { className: "mr-2" }),
|
|
3031
|
+
"Replace"
|
|
3032
|
+
]
|
|
3160
3033
|
}
|
|
3161
3034
|
),
|
|
3162
|
-
/* @__PURE__ */
|
|
3035
|
+
/* @__PURE__ */ jsxs(
|
|
3163
3036
|
Button,
|
|
3164
3037
|
{
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3038
|
+
variant: "danger",
|
|
3039
|
+
disabled: isUploading,
|
|
3040
|
+
onClick: handleDelete,
|
|
3041
|
+
children: [
|
|
3042
|
+
/* @__PURE__ */ jsx(Trash, { className: "mr-2" }),
|
|
3043
|
+
"Delete"
|
|
3044
|
+
]
|
|
3171
3045
|
}
|
|
3172
3046
|
)
|
|
3047
|
+
] })
|
|
3048
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
3049
|
+
"div",
|
|
3050
|
+
{
|
|
3051
|
+
className: clx(
|
|
3052
|
+
"flex flex-col items-center justify-center rounded-lg border-2 border-dashed transition-colors",
|
|
3053
|
+
isThumbnail ? "h-48" : "h-64",
|
|
3054
|
+
isDragging ? "border-ui-border-interactive bg-ui-bg-highlight" : "border-ui-border-base bg-ui-bg-subtle",
|
|
3055
|
+
!isUploading && "cursor-pointer"
|
|
3056
|
+
),
|
|
3057
|
+
onDragEnter: handleDragEnter,
|
|
3058
|
+
onDragLeave: handleDragLeave,
|
|
3059
|
+
onDragOver: handleDragOver,
|
|
3060
|
+
onDrop: handleDrop,
|
|
3061
|
+
onClick: () => {
|
|
3062
|
+
var _a;
|
|
3063
|
+
return !isUploading && ((_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click());
|
|
3064
|
+
},
|
|
3065
|
+
children: [
|
|
3066
|
+
/* @__PURE__ */ jsx(PhotoSolid, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }),
|
|
3067
|
+
/* @__PURE__ */ jsx(Text, { className: "mb-2 text-lg font-medium", children: isDragging ? `Drop ${imageType} here` : `Upload ${isThumbnail ? "Thumbnail" : "Image"}` }),
|
|
3068
|
+
/* @__PURE__ */ jsx(Text, { className: "mb-4 text-center text-ui-fg-subtle", children: "Drag and drop an image here, or click to select" }),
|
|
3069
|
+
/* @__PURE__ */ jsxs(Text, { className: "text-sm text-ui-fg-subtle", children: [
|
|
3070
|
+
formatFileTypes(),
|
|
3071
|
+
" • Max ",
|
|
3072
|
+
maxSize,
|
|
3073
|
+
"MB"
|
|
3074
|
+
] }),
|
|
3075
|
+
isThumbnail && /* @__PURE__ */ jsx(Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: 16:9 aspect ratio, minimum 400x225px" }),
|
|
3076
|
+
!isThumbnail && /* @__PURE__ */ jsx(Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: High resolution, minimum 1200x600px" }),
|
|
3077
|
+
isUploading && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(Text, { children: "Uploading..." }) })
|
|
3078
|
+
]
|
|
3079
|
+
}
|
|
3080
|
+
),
|
|
3081
|
+
/* @__PURE__ */ jsx(
|
|
3082
|
+
"input",
|
|
3083
|
+
{
|
|
3084
|
+
id: `${imageType}-file-input`,
|
|
3085
|
+
type: "file",
|
|
3086
|
+
accept: allowedTypes.join(","),
|
|
3087
|
+
onChange: (e2) => {
|
|
3088
|
+
var _a;
|
|
3089
|
+
const file = (_a = e2.target.files) == null ? void 0 : _a[0];
|
|
3090
|
+
if (file) handleFileUpload(file);
|
|
3091
|
+
e2.target.value = "";
|
|
3092
|
+
},
|
|
3093
|
+
className: "hidden"
|
|
3094
|
+
}
|
|
3095
|
+
)
|
|
3096
|
+
] }),
|
|
3097
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-between border-t pt-4", children: [
|
|
3098
|
+
/* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: isThumbnail ? "Thumbnail will be displayed in campaign listings" : "Main image for the campaign detail page" }),
|
|
3099
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: "Close" })
|
|
3100
|
+
] })
|
|
3101
|
+
] }) });
|
|
3102
|
+
};
|
|
3103
|
+
const CampaignDetailForm = ({
|
|
3104
|
+
campaignId,
|
|
3105
|
+
campaignName
|
|
3106
|
+
}) => {
|
|
3107
|
+
const [campaignDetail, setCampaignDetail] = useState(
|
|
3108
|
+
null
|
|
3109
|
+
);
|
|
3110
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
3111
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
3112
|
+
const [showImageUploader, setShowImageUploader] = useState(null);
|
|
3113
|
+
const [formData, setFormData] = useState({
|
|
3114
|
+
detail_content: "",
|
|
3115
|
+
terms_and_conditions: "",
|
|
3116
|
+
meta_title: "",
|
|
3117
|
+
meta_description: "",
|
|
3118
|
+
meta_keywords: "",
|
|
3119
|
+
link_url: "",
|
|
3120
|
+
link_text: "",
|
|
3121
|
+
display_order: 0
|
|
3122
|
+
});
|
|
3123
|
+
useEffect(() => {
|
|
3124
|
+
fetchCampaignDetail();
|
|
3125
|
+
}, [campaignId]);
|
|
3126
|
+
const fetchCampaignDetail = async () => {
|
|
3127
|
+
setIsLoading(true);
|
|
3128
|
+
try {
|
|
3129
|
+
const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
|
|
3130
|
+
credentials: "include"
|
|
3131
|
+
});
|
|
3132
|
+
if (response.ok) {
|
|
3133
|
+
const data = await response.json();
|
|
3134
|
+
if (data.campaign_detail) {
|
|
3135
|
+
console.log("Campaign detail loaded:", {
|
|
3136
|
+
image_url: data.campaign_detail.image_url,
|
|
3137
|
+
thumbnail_url: data.campaign_detail.thumbnail_url,
|
|
3138
|
+
decoded_image: data.campaign_detail.image_url ? decodeURIComponent(data.campaign_detail.image_url) : null,
|
|
3139
|
+
decoded_thumbnail: data.campaign_detail.thumbnail_url ? decodeURIComponent(data.campaign_detail.thumbnail_url) : null
|
|
3140
|
+
});
|
|
3141
|
+
setCampaignDetail(data.campaign_detail);
|
|
3142
|
+
setFormData({
|
|
3143
|
+
detail_content: data.campaign_detail.detail_content || "",
|
|
3144
|
+
terms_and_conditions: data.campaign_detail.terms_and_conditions || "",
|
|
3145
|
+
meta_title: data.campaign_detail.meta_title || "",
|
|
3146
|
+
meta_description: data.campaign_detail.meta_description || "",
|
|
3147
|
+
meta_keywords: data.campaign_detail.meta_keywords || "",
|
|
3148
|
+
link_url: data.campaign_detail.link_url || "",
|
|
3149
|
+
link_text: data.campaign_detail.link_text || "",
|
|
3150
|
+
display_order: data.campaign_detail.display_order || 0
|
|
3151
|
+
});
|
|
3152
|
+
}
|
|
3153
|
+
} else if (response.status !== 404) {
|
|
3154
|
+
console.error("Failed to fetch campaign detail");
|
|
3155
|
+
}
|
|
3156
|
+
} catch (error) {
|
|
3157
|
+
console.error("Error fetching campaign detail:", error);
|
|
3158
|
+
} finally {
|
|
3159
|
+
setIsLoading(false);
|
|
3160
|
+
}
|
|
3161
|
+
};
|
|
3162
|
+
const handleSave = async () => {
|
|
3163
|
+
setIsSaving(true);
|
|
3164
|
+
try {
|
|
3165
|
+
const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
|
|
3166
|
+
method: "POST",
|
|
3167
|
+
headers: {
|
|
3168
|
+
"Content-Type": "application/json"
|
|
3169
|
+
},
|
|
3170
|
+
body: JSON.stringify(formData),
|
|
3171
|
+
credentials: "include"
|
|
3172
|
+
});
|
|
3173
|
+
if (response.ok) {
|
|
3174
|
+
const data = await response.json();
|
|
3175
|
+
setCampaignDetail(data.campaign_detail);
|
|
3176
|
+
toast.success("Campaign details saved successfully");
|
|
3177
|
+
} else {
|
|
3178
|
+
const errorData = await response.json();
|
|
3179
|
+
toast.error(errorData.error || "Failed to save campaign details");
|
|
3180
|
+
}
|
|
3181
|
+
} catch (error) {
|
|
3182
|
+
console.error("Error saving campaign detail:", error);
|
|
3183
|
+
toast.error("Error saving campaign details");
|
|
3184
|
+
} finally {
|
|
3185
|
+
setIsSaving(false);
|
|
3186
|
+
}
|
|
3187
|
+
};
|
|
3188
|
+
if (isLoading) {
|
|
3189
|
+
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { className: "py-8 text-center", children: "Loading campaign details..." }) });
|
|
3190
|
+
}
|
|
3191
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3192
|
+
/* @__PURE__ */ jsxs(Container, { className: "space-y-6", children: [
|
|
3193
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3194
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3195
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Campaign Details" }),
|
|
3196
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-ui-fg-subtle", children: campaignName })
|
|
3173
3197
|
] }),
|
|
3174
|
-
/* @__PURE__ */ jsx(
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3198
|
+
/* @__PURE__ */ jsx(Button, { onClick: handleSave, isLoading: isSaving, children: "Save Details" })
|
|
3199
|
+
] }),
|
|
3200
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3201
|
+
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "Images" }),
|
|
3202
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
3203
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3204
|
+
/* @__PURE__ */ jsx(Label, { children: "Main Campaign Image" }),
|
|
3205
|
+
/* @__PURE__ */ jsx(
|
|
3206
|
+
"div",
|
|
3207
|
+
{
|
|
3208
|
+
className: clx(
|
|
3209
|
+
"relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
|
|
3210
|
+
(campaignDetail == null ? void 0 : campaignDetail.image_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
|
|
3211
|
+
),
|
|
3212
|
+
children: (campaignDetail == null ? void 0 : campaignDetail.image_url) ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3213
|
+
/* @__PURE__ */ jsx(
|
|
3214
|
+
"img",
|
|
3215
|
+
{
|
|
3216
|
+
src: campaignDetail.image_url,
|
|
3217
|
+
alt: "Campaign",
|
|
3218
|
+
className: "h-full w-full object-cover",
|
|
3219
|
+
onError: (e2) => {
|
|
3220
|
+
console.error(
|
|
3221
|
+
"Failed to load image:",
|
|
3222
|
+
campaignDetail.image_url
|
|
3223
|
+
);
|
|
3224
|
+
e2.currentTarget.style.display = "none";
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
),
|
|
3228
|
+
/* @__PURE__ */ jsxs(
|
|
3229
|
+
Button,
|
|
3230
|
+
{
|
|
3231
|
+
variant: "secondary",
|
|
3232
|
+
size: "small",
|
|
3233
|
+
className: "absolute bottom-2 right-2",
|
|
3234
|
+
onClick: () => setShowImageUploader("image"),
|
|
3235
|
+
children: [
|
|
3236
|
+
/* @__PURE__ */ jsx(PencilSquare, { className: "mr-1" }),
|
|
3237
|
+
"Change"
|
|
3238
|
+
]
|
|
3239
|
+
}
|
|
3240
|
+
)
|
|
3241
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
3242
|
+
Button,
|
|
3243
|
+
{
|
|
3244
|
+
variant: "secondary",
|
|
3245
|
+
onClick: () => setShowImageUploader("image"),
|
|
3246
|
+
children: [
|
|
3247
|
+
/* @__PURE__ */ jsx(PhotoSolid, { className: "mr-2" }),
|
|
3248
|
+
"Upload Image"
|
|
3249
|
+
]
|
|
3250
|
+
}
|
|
3251
|
+
)
|
|
3252
|
+
}
|
|
3253
|
+
)
|
|
3254
|
+
] }),
|
|
3255
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3256
|
+
/* @__PURE__ */ jsx(Label, { children: "Thumbnail" }),
|
|
3257
|
+
/* @__PURE__ */ jsx(
|
|
3258
|
+
"div",
|
|
3259
|
+
{
|
|
3260
|
+
className: clx(
|
|
3261
|
+
"relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
|
|
3262
|
+
(campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
|
|
3263
|
+
),
|
|
3264
|
+
children: (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3265
|
+
/* @__PURE__ */ jsx(
|
|
3266
|
+
"img",
|
|
3267
|
+
{
|
|
3268
|
+
src: campaignDetail.thumbnail_url,
|
|
3269
|
+
alt: "Thumbnail",
|
|
3270
|
+
className: "h-full w-full object-cover",
|
|
3271
|
+
onError: (e2) => {
|
|
3272
|
+
console.error(
|
|
3273
|
+
"Failed to load thumbnail:",
|
|
3274
|
+
campaignDetail.thumbnail_url
|
|
3275
|
+
);
|
|
3276
|
+
e2.currentTarget.style.display = "none";
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
),
|
|
3280
|
+
/* @__PURE__ */ jsxs(
|
|
3281
|
+
Button,
|
|
3282
|
+
{
|
|
3283
|
+
variant: "secondary",
|
|
3284
|
+
size: "small",
|
|
3285
|
+
className: "absolute bottom-2 right-2",
|
|
3286
|
+
onClick: () => setShowImageUploader("thumbnail"),
|
|
3287
|
+
children: [
|
|
3288
|
+
/* @__PURE__ */ jsx(PencilSquare, { className: "mr-1" }),
|
|
3289
|
+
"Change"
|
|
3290
|
+
]
|
|
3291
|
+
}
|
|
3292
|
+
)
|
|
3293
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
3294
|
+
Button,
|
|
3295
|
+
{
|
|
3296
|
+
variant: "secondary",
|
|
3297
|
+
onClick: () => setShowImageUploader("thumbnail"),
|
|
3298
|
+
children: [
|
|
3299
|
+
/* @__PURE__ */ jsx(PhotoSolid, { className: "mr-2" }),
|
|
3300
|
+
"Upload Thumbnail"
|
|
3301
|
+
]
|
|
3302
|
+
}
|
|
3303
|
+
)
|
|
3304
|
+
}
|
|
3305
|
+
)
|
|
3306
|
+
] })
|
|
3307
|
+
] })
|
|
3308
|
+
] }),
|
|
3309
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
3310
|
+
MarkdownEditor,
|
|
3188
3311
|
{
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3312
|
+
label: "Campaign Detail Content",
|
|
3313
|
+
value: formData.detail_content,
|
|
3314
|
+
onChange: (value) => setFormData({ ...formData, detail_content: value }),
|
|
3315
|
+
placeholder: "Enter campaign details in Markdown format. You can include images, links, code blocks, and more...",
|
|
3316
|
+
helpText: "Supports Markdown and code syntax",
|
|
3317
|
+
rows: 12,
|
|
3318
|
+
showPreview: true
|
|
3196
3319
|
}
|
|
3197
|
-
) })
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
/* @__PURE__ */ jsx(
|
|
3201
|
-
Button,
|
|
3202
|
-
{
|
|
3203
|
-
type: "button",
|
|
3204
|
-
variant: "secondary",
|
|
3205
|
-
size: "small",
|
|
3206
|
-
onClick: () => insertMarkdown("**", "**"),
|
|
3207
|
-
title: "Bold",
|
|
3208
|
-
children: /* @__PURE__ */ jsx("strong", { children: "B" })
|
|
3209
|
-
}
|
|
3210
|
-
),
|
|
3211
|
-
/* @__PURE__ */ jsx(
|
|
3212
|
-
Button,
|
|
3213
|
-
{
|
|
3214
|
-
type: "button",
|
|
3215
|
-
variant: "secondary",
|
|
3216
|
-
size: "small",
|
|
3217
|
-
onClick: () => insertMarkdown("*", "*"),
|
|
3218
|
-
title: "Italic",
|
|
3219
|
-
children: /* @__PURE__ */ jsx("em", { children: "I" })
|
|
3220
|
-
}
|
|
3221
|
-
),
|
|
3222
|
-
/* @__PURE__ */ jsx(
|
|
3223
|
-
Button,
|
|
3224
|
-
{
|
|
3225
|
-
type: "button",
|
|
3226
|
-
variant: "secondary",
|
|
3227
|
-
size: "small",
|
|
3228
|
-
onClick: () => insertMarkdown("`", "`"),
|
|
3229
|
-
title: "Inline Code",
|
|
3230
|
-
children: /* @__PURE__ */ jsx(CommandLine, { className: "h-4 w-4" })
|
|
3231
|
-
}
|
|
3232
|
-
),
|
|
3233
|
-
/* @__PURE__ */ jsx(
|
|
3234
|
-
Button,
|
|
3235
|
-
{
|
|
3236
|
-
type: "button",
|
|
3237
|
-
variant: "secondary",
|
|
3238
|
-
size: "small",
|
|
3239
|
-
onClick: () => insertMarkdown("```\n", "\n```"),
|
|
3240
|
-
title: "Code Block",
|
|
3241
|
-
children: "Code"
|
|
3242
|
-
}
|
|
3243
|
-
)
|
|
3244
|
-
] }),
|
|
3245
|
-
/* @__PURE__ */ jsx(
|
|
3246
|
-
Textarea,
|
|
3320
|
+
) }),
|
|
3321
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
3322
|
+
MarkdownEditor,
|
|
3247
3323
|
{
|
|
3248
|
-
|
|
3249
|
-
value,
|
|
3250
|
-
onChange: (
|
|
3251
|
-
placeholder,
|
|
3252
|
-
|
|
3253
|
-
|
|
3324
|
+
label: "Terms and Conditions",
|
|
3325
|
+
value: formData.terms_and_conditions,
|
|
3326
|
+
onChange: (value) => setFormData({ ...formData, terms_and_conditions: value }),
|
|
3327
|
+
placeholder: "Enter terms and conditions for this campaign...",
|
|
3328
|
+
helpText: "Optional - Campaign specific terms",
|
|
3329
|
+
rows: 8,
|
|
3330
|
+
showPreview: true
|
|
3254
3331
|
}
|
|
3255
|
-
)
|
|
3332
|
+
) }),
|
|
3333
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3334
|
+
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "Call to Action Link" }),
|
|
3335
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
3336
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3337
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "link-url", children: "Link URL" }),
|
|
3338
|
+
/* @__PURE__ */ jsx(
|
|
3339
|
+
Input,
|
|
3340
|
+
{
|
|
3341
|
+
id: "link-url",
|
|
3342
|
+
type: "url",
|
|
3343
|
+
value: formData.link_url,
|
|
3344
|
+
onChange: (e2) => setFormData({ ...formData, link_url: e2.target.value }),
|
|
3345
|
+
placeholder: "https://example.com/campaign"
|
|
3346
|
+
}
|
|
3347
|
+
)
|
|
3348
|
+
] }),
|
|
3349
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3350
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "link-text", children: "Link Text" }),
|
|
3351
|
+
/* @__PURE__ */ jsx(
|
|
3352
|
+
Input,
|
|
3353
|
+
{
|
|
3354
|
+
id: "link-text",
|
|
3355
|
+
value: formData.link_text,
|
|
3356
|
+
onChange: (e2) => setFormData({ ...formData, link_text: e2.target.value }),
|
|
3357
|
+
placeholder: "Shop Now"
|
|
3358
|
+
}
|
|
3359
|
+
)
|
|
3360
|
+
] })
|
|
3361
|
+
] })
|
|
3362
|
+
] }),
|
|
3363
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3364
|
+
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "SEO & Metadata" }),
|
|
3365
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3366
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3367
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "meta-title", children: "Meta Title" }),
|
|
3368
|
+
/* @__PURE__ */ jsx(
|
|
3369
|
+
Input,
|
|
3370
|
+
{
|
|
3371
|
+
id: "meta-title",
|
|
3372
|
+
value: formData.meta_title,
|
|
3373
|
+
onChange: (e2) => setFormData({ ...formData, meta_title: e2.target.value }),
|
|
3374
|
+
placeholder: "Campaign meta title for SEO"
|
|
3375
|
+
}
|
|
3376
|
+
)
|
|
3377
|
+
] }),
|
|
3378
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3379
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "meta-description", children: "Meta Description" }),
|
|
3380
|
+
/* @__PURE__ */ jsx(
|
|
3381
|
+
Input,
|
|
3382
|
+
{
|
|
3383
|
+
id: "meta-description",
|
|
3384
|
+
value: formData.meta_description,
|
|
3385
|
+
onChange: (e2) => setFormData({
|
|
3386
|
+
...formData,
|
|
3387
|
+
meta_description: e2.target.value
|
|
3388
|
+
}),
|
|
3389
|
+
placeholder: "Campaign meta description for SEO"
|
|
3390
|
+
}
|
|
3391
|
+
)
|
|
3392
|
+
] }),
|
|
3393
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3394
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "meta-keywords", children: "Meta Keywords" }),
|
|
3395
|
+
/* @__PURE__ */ jsx(
|
|
3396
|
+
Input,
|
|
3397
|
+
{
|
|
3398
|
+
id: "meta-keywords",
|
|
3399
|
+
value: formData.meta_keywords,
|
|
3400
|
+
onChange: (e2) => setFormData({
|
|
3401
|
+
...formData,
|
|
3402
|
+
meta_keywords: e2.target.value
|
|
3403
|
+
}),
|
|
3404
|
+
placeholder: "keyword1, keyword2, keyword3"
|
|
3405
|
+
}
|
|
3406
|
+
),
|
|
3407
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Comma-separated keywords for SEO" })
|
|
3408
|
+
] }),
|
|
3409
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3410
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "display-order", children: "Display Order" }),
|
|
3411
|
+
/* @__PURE__ */ jsx(
|
|
3412
|
+
Input,
|
|
3413
|
+
{
|
|
3414
|
+
id: "display-order",
|
|
3415
|
+
type: "number",
|
|
3416
|
+
value: formData.display_order,
|
|
3417
|
+
onChange: (e2) => setFormData({
|
|
3418
|
+
...formData,
|
|
3419
|
+
display_order: parseInt(e2.target.value) || 0
|
|
3420
|
+
}),
|
|
3421
|
+
placeholder: "0"
|
|
3422
|
+
}
|
|
3423
|
+
),
|
|
3424
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Lower numbers appear first in listings" })
|
|
3425
|
+
] })
|
|
3426
|
+
] })
|
|
3427
|
+
] }),
|
|
3428
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end border-t pt-4", children: /* @__PURE__ */ jsx(Button, { onClick: handleSave, isLoading: isSaving, children: "Save Campaign Details" }) })
|
|
3256
3429
|
] }),
|
|
3257
|
-
/* @__PURE__ */ jsx(
|
|
3430
|
+
showImageUploader && /* @__PURE__ */ jsx(
|
|
3431
|
+
CampaignImageUploader,
|
|
3432
|
+
{
|
|
3433
|
+
campaignId,
|
|
3434
|
+
imageType: showImageUploader,
|
|
3435
|
+
currentImageUrl: showImageUploader === "image" ? campaignDetail == null ? void 0 : campaignDetail.image_url : campaignDetail == null ? void 0 : campaignDetail.thumbnail_url,
|
|
3436
|
+
onClose: () => setShowImageUploader(null),
|
|
3437
|
+
onSuccess: fetchCampaignDetail
|
|
3438
|
+
}
|
|
3439
|
+
)
|
|
3258
3440
|
] });
|
|
3259
3441
|
};
|
|
3260
|
-
const
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
const
|
|
3273
|
-
const
|
|
3274
|
-
const
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
}
|
|
3287
|
-
return true;
|
|
3442
|
+
const CouponDetail = () => {
|
|
3443
|
+
var _a, _b, _c, _d, _e, _f;
|
|
3444
|
+
const { id } = useParams();
|
|
3445
|
+
const navigate = useNavigate();
|
|
3446
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
3447
|
+
const { data, isLoading, refetch } = useCouponById(id ?? "");
|
|
3448
|
+
if (isLoading) {
|
|
3449
|
+
return /* @__PURE__ */ jsx(Container, { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx("div", { className: "animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent" }) });
|
|
3450
|
+
}
|
|
3451
|
+
if (!(data == null ? void 0 : data.campaign)) {
|
|
3452
|
+
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("p", { children: "Coupon not found" }) });
|
|
3453
|
+
}
|
|
3454
|
+
const campaign = data.campaign;
|
|
3455
|
+
const promotion = (_a = campaign.promotions) == null ? void 0 : _a[0];
|
|
3456
|
+
const initialValues = {
|
|
3457
|
+
name: campaign.name ?? "",
|
|
3458
|
+
description: campaign.description ?? "",
|
|
3459
|
+
type: "coupon",
|
|
3460
|
+
code: (promotion == null ? void 0 : promotion.code) ?? "",
|
|
3461
|
+
discount_type: ((_b = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _b.type) ?? "percentage",
|
|
3462
|
+
discount_value: Number(((_c = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _c.value) ?? 0),
|
|
3463
|
+
currency_code: ((_d = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _d.currency_code) ?? void 0,
|
|
3464
|
+
starts_at: campaign.starts_at ? dayjs(campaign.starts_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
3465
|
+
ends_at: campaign.ends_at ? dayjs(campaign.ends_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
3466
|
+
allocation: ((_e = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _e.allocation) ?? "total",
|
|
3467
|
+
target_type: ((_f = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _f.target_type) ?? "order"
|
|
3288
3468
|
};
|
|
3289
|
-
const
|
|
3290
|
-
var
|
|
3291
|
-
if (!
|
|
3292
|
-
|
|
3293
|
-
const reader = new FileReader();
|
|
3294
|
-
reader.onloadend = () => {
|
|
3295
|
-
setPreviewUrl(reader.result);
|
|
3296
|
-
};
|
|
3297
|
-
reader.readAsDataURL(file);
|
|
3298
|
-
setIsUploading(true);
|
|
3299
|
-
const formData = new FormData();
|
|
3300
|
-
formData.append("file", file);
|
|
3301
|
-
try {
|
|
3302
|
-
const response = await fetch(
|
|
3303
|
-
`/admin/campaigns/${campaignId}/${imageType}`,
|
|
3304
|
-
{
|
|
3305
|
-
method: "POST",
|
|
3306
|
-
body: formData,
|
|
3307
|
-
credentials: "include"
|
|
3308
|
-
}
|
|
3309
|
-
);
|
|
3310
|
-
if (response.ok) {
|
|
3311
|
-
const result = await response.json();
|
|
3312
|
-
const newImageUrl = imageType === "image" ? (_a = result.campaign_detail) == null ? void 0 : _a.image_url : (_b = result.campaign_detail) == null ? void 0 : _b.thumbnail_url;
|
|
3313
|
-
setDisplayImage(newImageUrl);
|
|
3314
|
-
setPreviewUrl(null);
|
|
3315
|
-
onSuccess == null ? void 0 : onSuccess();
|
|
3316
|
-
setTimeout(() => {
|
|
3317
|
-
onClose();
|
|
3318
|
-
}, 1e3);
|
|
3319
|
-
} else {
|
|
3320
|
-
const errorData = await response.json();
|
|
3321
|
-
setError(errorData.error || `Failed to upload ${imageType}`);
|
|
3322
|
-
setPreviewUrl(null);
|
|
3323
|
-
}
|
|
3324
|
-
} catch (err) {
|
|
3325
|
-
setError(`Error uploading ${imageType}`);
|
|
3326
|
-
setPreviewUrl(null);
|
|
3327
|
-
console.error(`Error uploading ${imageType}:`, err);
|
|
3328
|
-
} finally {
|
|
3329
|
-
setIsUploading(false);
|
|
3469
|
+
const handleSubmit = async (formData) => {
|
|
3470
|
+
var _a2, _b2;
|
|
3471
|
+
if (!id) {
|
|
3472
|
+
return;
|
|
3330
3473
|
}
|
|
3331
|
-
};
|
|
3332
|
-
const handleDelete = async () => {
|
|
3333
|
-
if (!confirm(`Are you sure you want to delete the ${imageType}?`)) return;
|
|
3334
|
-
setIsUploading(true);
|
|
3335
|
-
setError(null);
|
|
3336
3474
|
try {
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
);
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
setError(`Error deleting ${imageType}`);
|
|
3357
|
-
console.error(`Error deleting ${imageType}:`, err);
|
|
3358
|
-
} finally {
|
|
3359
|
-
setIsUploading(false);
|
|
3475
|
+
await axios.put(`/admin/coupons/${id}`, {
|
|
3476
|
+
...formData,
|
|
3477
|
+
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
3478
|
+
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
3479
|
+
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
3480
|
+
});
|
|
3481
|
+
toast.success("Coupon updated successfully");
|
|
3482
|
+
setIsEditing(false);
|
|
3483
|
+
refetch();
|
|
3484
|
+
} catch (error) {
|
|
3485
|
+
let message = "Failed to update coupon";
|
|
3486
|
+
if (axios.isAxiosError(error)) {
|
|
3487
|
+
message = ((_b2 = (_a2 = error.response) == null ? void 0 : _a2.data) == null ? void 0 : _b2.message) ?? message;
|
|
3488
|
+
} else if (error instanceof Error) {
|
|
3489
|
+
message = error.message;
|
|
3490
|
+
}
|
|
3491
|
+
toast.error("Failed to update coupon", {
|
|
3492
|
+
description: message
|
|
3493
|
+
});
|
|
3360
3494
|
}
|
|
3361
3495
|
};
|
|
3362
|
-
|
|
3363
|
-
e2.preventDefault();
|
|
3364
|
-
e2.stopPropagation();
|
|
3365
|
-
setIsDragging(true);
|
|
3366
|
-
}, []);
|
|
3367
|
-
const handleDragLeave = useCallback((e2) => {
|
|
3368
|
-
e2.preventDefault();
|
|
3369
|
-
e2.stopPropagation();
|
|
3370
|
-
setIsDragging(false);
|
|
3371
|
-
}, []);
|
|
3372
|
-
const handleDragOver = useCallback((e2) => {
|
|
3373
|
-
e2.preventDefault();
|
|
3374
|
-
e2.stopPropagation();
|
|
3375
|
-
}, []);
|
|
3376
|
-
const handleDrop = useCallback((e2) => {
|
|
3377
|
-
var _a;
|
|
3378
|
-
e2.preventDefault();
|
|
3379
|
-
e2.stopPropagation();
|
|
3380
|
-
setIsDragging(false);
|
|
3381
|
-
const file = (_a = e2.dataTransfer.files) == null ? void 0 : _a[0];
|
|
3382
|
-
if (file) {
|
|
3383
|
-
handleFileUpload(file);
|
|
3384
|
-
}
|
|
3385
|
-
}, []);
|
|
3386
|
-
const imageToDisplay = previewUrl || displayImage;
|
|
3387
|
-
return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-2xl rounded-lg bg-ui-bg-base p-6", children: [
|
|
3496
|
+
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
3388
3497
|
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
3389
3498
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
3390
|
-
/* @__PURE__ */
|
|
3391
|
-
|
|
3392
|
-
isThumbnail ? "Thumbnail" : "Image"
|
|
3393
|
-
] }),
|
|
3394
|
-
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: isThumbnail ? "Thumbnail for campaign listing" : "Main campaign image" })
|
|
3499
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold mb-1", children: campaign.name }),
|
|
3500
|
+
/* @__PURE__ */ jsx("p", { className: "text-ui-fg-subtle", children: "Manage coupon configuration and content" })
|
|
3395
3501
|
] }),
|
|
3396
|
-
/* @__PURE__ */
|
|
3502
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3503
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/coupons"), children: "Back to Coupons" }),
|
|
3504
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
|
|
3505
|
+
] })
|
|
3397
3506
|
] }),
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
/* @__PURE__ */
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
src: imageToDisplay,
|
|
3406
|
-
alt: `Campaign ${imageType}`,
|
|
3407
|
-
className: clx(
|
|
3408
|
-
"w-full object-contain",
|
|
3409
|
-
isThumbnail ? "h-32" : "h-64"
|
|
3410
|
-
)
|
|
3411
|
-
}
|
|
3412
|
-
),
|
|
3413
|
-
isUploading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsx("div", { className: "text-white", children: "Uploading..." }) })
|
|
3414
|
-
] }),
|
|
3415
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3416
|
-
/* @__PURE__ */ jsxs(
|
|
3417
|
-
Button,
|
|
3418
|
-
{
|
|
3419
|
-
variant: "secondary",
|
|
3420
|
-
disabled: isUploading,
|
|
3421
|
-
onClick: () => {
|
|
3422
|
-
var _a;
|
|
3423
|
-
return (_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click();
|
|
3424
|
-
},
|
|
3425
|
-
children: [
|
|
3426
|
-
/* @__PURE__ */ jsx(CloudArrowUp, { className: "mr-2" }),
|
|
3427
|
-
"Replace"
|
|
3428
|
-
]
|
|
3429
|
-
}
|
|
3430
|
-
),
|
|
3431
|
-
/* @__PURE__ */ jsxs(
|
|
3432
|
-
Button,
|
|
3433
|
-
{
|
|
3434
|
-
variant: "danger",
|
|
3435
|
-
disabled: isUploading,
|
|
3436
|
-
onClick: handleDelete,
|
|
3437
|
-
children: [
|
|
3438
|
-
/* @__PURE__ */ jsx(Trash, { className: "mr-2" }),
|
|
3439
|
-
"Delete"
|
|
3440
|
-
]
|
|
3441
|
-
}
|
|
3442
|
-
)
|
|
3443
|
-
] })
|
|
3444
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
3445
|
-
"div",
|
|
3507
|
+
/* @__PURE__ */ jsxs(Tabs, { defaultValue: "settings", children: [
|
|
3508
|
+
/* @__PURE__ */ jsxs(Tabs.List, { children: [
|
|
3509
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "settings", children: "Coupon Settings" }),
|
|
3510
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "details", children: "Coupon Details" })
|
|
3511
|
+
] }),
|
|
3512
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
3513
|
+
CouponForm,
|
|
3446
3514
|
{
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
!isUploading && "cursor-pointer"
|
|
3452
|
-
),
|
|
3453
|
-
onDragEnter: handleDragEnter,
|
|
3454
|
-
onDragLeave: handleDragLeave,
|
|
3455
|
-
onDragOver: handleDragOver,
|
|
3456
|
-
onDrop: handleDrop,
|
|
3457
|
-
onClick: () => {
|
|
3458
|
-
var _a;
|
|
3459
|
-
return !isUploading && ((_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click());
|
|
3460
|
-
},
|
|
3461
|
-
children: [
|
|
3462
|
-
/* @__PURE__ */ jsx(PhotoSolid, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }),
|
|
3463
|
-
/* @__PURE__ */ jsx(Text, { className: "mb-2 text-lg font-medium", children: isDragging ? `Drop ${imageType} here` : `Upload ${isThumbnail ? "Thumbnail" : "Image"}` }),
|
|
3464
|
-
/* @__PURE__ */ jsx(Text, { className: "mb-4 text-center text-ui-fg-subtle", children: "Drag and drop an image here, or click to select" }),
|
|
3465
|
-
/* @__PURE__ */ jsxs(Text, { className: "text-sm text-ui-fg-subtle", children: [
|
|
3466
|
-
formatFileTypes(),
|
|
3467
|
-
" • Max ",
|
|
3468
|
-
maxSize,
|
|
3469
|
-
"MB"
|
|
3470
|
-
] }),
|
|
3471
|
-
isThumbnail && /* @__PURE__ */ jsx(Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: 16:9 aspect ratio, minimum 400x225px" }),
|
|
3472
|
-
!isThumbnail && /* @__PURE__ */ jsx(Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: High resolution, minimum 1200x600px" }),
|
|
3473
|
-
isUploading && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(Text, { children: "Uploading..." }) })
|
|
3474
|
-
]
|
|
3515
|
+
initialData: initialValues,
|
|
3516
|
+
onSubmit: handleSubmit,
|
|
3517
|
+
onCancel: () => navigate("/coupons"),
|
|
3518
|
+
disabled: !isEditing
|
|
3475
3519
|
}
|
|
3476
|
-
),
|
|
3477
|
-
/* @__PURE__ */ jsx(
|
|
3478
|
-
|
|
3520
|
+
) }),
|
|
3521
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
3522
|
+
CampaignDetailForm,
|
|
3479
3523
|
{
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
accept: allowedTypes.join(","),
|
|
3483
|
-
onChange: (e2) => {
|
|
3484
|
-
var _a;
|
|
3485
|
-
const file = (_a = e2.target.files) == null ? void 0 : _a[0];
|
|
3486
|
-
if (file) handleFileUpload(file);
|
|
3487
|
-
e2.target.value = "";
|
|
3488
|
-
},
|
|
3489
|
-
className: "hidden"
|
|
3524
|
+
campaignId: campaign.id,
|
|
3525
|
+
campaignName: campaign.name ?? ""
|
|
3490
3526
|
}
|
|
3491
|
-
)
|
|
3492
|
-
] }),
|
|
3493
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-between border-t pt-4", children: [
|
|
3494
|
-
/* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: isThumbnail ? "Thumbnail will be displayed in campaign listings" : "Main image for the campaign detail page" }),
|
|
3495
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: "Close" })
|
|
3527
|
+
) })
|
|
3496
3528
|
] })
|
|
3497
|
-
] })
|
|
3529
|
+
] });
|
|
3530
|
+
};
|
|
3531
|
+
const config$1 = defineRouteConfig({
|
|
3532
|
+
label: "Coupon Detail",
|
|
3533
|
+
icon: Tag
|
|
3534
|
+
});
|
|
3535
|
+
const CouponCreate = () => {
|
|
3536
|
+
const navigate = useNavigate();
|
|
3537
|
+
const handleSubmit = async (formData) => {
|
|
3538
|
+
var _a, _b;
|
|
3539
|
+
try {
|
|
3540
|
+
await axios.post("/admin/coupons", {
|
|
3541
|
+
...formData,
|
|
3542
|
+
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
3543
|
+
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
3544
|
+
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
3545
|
+
});
|
|
3546
|
+
toast.success("Coupon created successfully");
|
|
3547
|
+
navigate("/coupons");
|
|
3548
|
+
} catch (error) {
|
|
3549
|
+
let message = "Failed to create coupon";
|
|
3550
|
+
if (axios.isAxiosError(error)) {
|
|
3551
|
+
message = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) ?? message;
|
|
3552
|
+
} else if (error instanceof Error) {
|
|
3553
|
+
message = error.message;
|
|
3554
|
+
}
|
|
3555
|
+
toast.error("Failed to create coupon", {
|
|
3556
|
+
description: message
|
|
3557
|
+
});
|
|
3558
|
+
}
|
|
3559
|
+
};
|
|
3560
|
+
return /* @__PURE__ */ jsx(
|
|
3561
|
+
CouponForm,
|
|
3562
|
+
{
|
|
3563
|
+
onSubmit: handleSubmit,
|
|
3564
|
+
onCancel: () => navigate("/coupons")
|
|
3565
|
+
}
|
|
3566
|
+
);
|
|
3567
|
+
};
|
|
3568
|
+
const sdk = new Medusa({
|
|
3569
|
+
baseUrl: "/",
|
|
3570
|
+
debug: false,
|
|
3571
|
+
auth: {
|
|
3572
|
+
type: "session"
|
|
3573
|
+
}
|
|
3574
|
+
});
|
|
3575
|
+
const columnHelper = createDataTableColumnHelper();
|
|
3576
|
+
const columns = [
|
|
3577
|
+
columnHelper.accessor("title", {
|
|
3578
|
+
header: "Title",
|
|
3579
|
+
cell: (info) => /* @__PURE__ */ jsx(Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
3580
|
+
}),
|
|
3581
|
+
columnHelper.accessor("description", {
|
|
3582
|
+
header: "Description",
|
|
3583
|
+
cell: (info) => /* @__PURE__ */ jsx(Text, { size: "large", className: "py-2", children: info.getValue() })
|
|
3584
|
+
})
|
|
3585
|
+
];
|
|
3586
|
+
const ProductSelector = ({
|
|
3587
|
+
selectedProductIds,
|
|
3588
|
+
onSelectProduct
|
|
3589
|
+
}) => {
|
|
3590
|
+
const [search, setSearch] = useState("");
|
|
3591
|
+
const debouncedSearchHandler = debounce((value) => {
|
|
3592
|
+
setSearch(value);
|
|
3593
|
+
}, 500);
|
|
3594
|
+
const [products, setProducts] = useState(null);
|
|
3595
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
3596
|
+
useEffect(() => {
|
|
3597
|
+
const fetchProducts = async () => {
|
|
3598
|
+
setIsLoading(true);
|
|
3599
|
+
try {
|
|
3600
|
+
const result = await sdk.admin.product.list({
|
|
3601
|
+
q: search,
|
|
3602
|
+
limit: 100
|
|
3603
|
+
});
|
|
3604
|
+
setProducts(result);
|
|
3605
|
+
} catch (error) {
|
|
3606
|
+
console.error("Failed to fetch products:", error);
|
|
3607
|
+
} finally {
|
|
3608
|
+
setIsLoading(false);
|
|
3609
|
+
}
|
|
3610
|
+
};
|
|
3611
|
+
fetchProducts();
|
|
3612
|
+
}, [selectedProductIds, search]);
|
|
3613
|
+
const selectableProducts = useMemo(() => {
|
|
3614
|
+
var _a;
|
|
3615
|
+
return (_a = products == null ? void 0 : products.products) == null ? void 0 : _a.filter(
|
|
3616
|
+
(product) => !selectedProductIds.includes(product.id)
|
|
3617
|
+
);
|
|
3618
|
+
}, [products == null ? void 0 : products.products, selectedProductIds]);
|
|
3619
|
+
const table = useDataTable({
|
|
3620
|
+
data: selectableProducts || [],
|
|
3621
|
+
columns,
|
|
3622
|
+
getRowId: (product) => product.id,
|
|
3623
|
+
onRowClick: (_, row) => {
|
|
3624
|
+
onSelectProduct == null ? void 0 : onSelectProduct(row.original);
|
|
3625
|
+
}
|
|
3626
|
+
});
|
|
3627
|
+
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
3628
|
+
/* @__PURE__ */ jsx(
|
|
3629
|
+
Input,
|
|
3630
|
+
{
|
|
3631
|
+
className: "text-lg py-2",
|
|
3632
|
+
placeholder: "Search products...",
|
|
3633
|
+
onChange: (e2) => debouncedSearchHandler(e2.target.value)
|
|
3634
|
+
}
|
|
3635
|
+
),
|
|
3636
|
+
/* @__PURE__ */ jsx(DataTable, { instance: table, children: /* @__PURE__ */ jsx(DataTable.Table, {}) })
|
|
3637
|
+
] });
|
|
3498
3638
|
};
|
|
3499
|
-
const
|
|
3500
|
-
|
|
3501
|
-
|
|
3639
|
+
const customCampaignSchema = z.object({
|
|
3640
|
+
name: z.string().min(1, "Name is required"),
|
|
3641
|
+
description: z.string().min(1, "Description is required"),
|
|
3642
|
+
type: z.literal("flash-sale"),
|
|
3643
|
+
starts_at: z.string().min(1, "Start date is required"),
|
|
3644
|
+
ends_at: z.string().min(1, "End date is required")
|
|
3645
|
+
});
|
|
3646
|
+
const campaignProductSchema = z.object({
|
|
3647
|
+
product: z.object({
|
|
3648
|
+
id: z.string(),
|
|
3649
|
+
title: z.string()
|
|
3650
|
+
}),
|
|
3651
|
+
discountType: z.enum([
|
|
3652
|
+
"percentage"
|
|
3653
|
+
// , "fixed"
|
|
3654
|
+
]),
|
|
3655
|
+
discountValue: z.number().min(1),
|
|
3656
|
+
limit: z.number().min(1),
|
|
3657
|
+
maxQty: z.number().min(1)
|
|
3658
|
+
});
|
|
3659
|
+
const campaignDataSchema = customCampaignSchema.extend({
|
|
3660
|
+
products: z.array(campaignProductSchema).min(1, "At least one product is required")
|
|
3661
|
+
}).refine(
|
|
3662
|
+
(data) => new Date(data.starts_at) < new Date(data.ends_at),
|
|
3663
|
+
"End date must be after start date"
|
|
3664
|
+
);
|
|
3665
|
+
const FlashSaleForm = ({
|
|
3666
|
+
initialData,
|
|
3667
|
+
initialProducts,
|
|
3668
|
+
onSubmit,
|
|
3669
|
+
onCancel,
|
|
3670
|
+
disabled = false
|
|
3502
3671
|
}) => {
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3672
|
+
var _a;
|
|
3673
|
+
const {
|
|
3674
|
+
control,
|
|
3675
|
+
register,
|
|
3676
|
+
handleSubmit,
|
|
3677
|
+
watch,
|
|
3678
|
+
setValue,
|
|
3679
|
+
formState: { errors }
|
|
3680
|
+
} = useForm({
|
|
3681
|
+
resolver: t(campaignDataSchema),
|
|
3682
|
+
defaultValues: {
|
|
3683
|
+
name: (initialData == null ? void 0 : initialData.name) || "",
|
|
3684
|
+
description: (initialData == null ? void 0 : initialData.description) || "",
|
|
3685
|
+
type: "flash-sale",
|
|
3686
|
+
starts_at: (initialData == null ? void 0 : initialData.starts_at) || "",
|
|
3687
|
+
ends_at: (initialData == null ? void 0 : initialData.ends_at) || "",
|
|
3688
|
+
products: initialProducts ? Array.from(initialProducts.values()) : []
|
|
3689
|
+
}
|
|
3518
3690
|
});
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
decoded_image: data.campaign_detail.image_url ? decodeURIComponent(data.campaign_detail.image_url) : null,
|
|
3535
|
-
decoded_thumbnail: data.campaign_detail.thumbnail_url ? decodeURIComponent(data.campaign_detail.thumbnail_url) : null
|
|
3536
|
-
});
|
|
3537
|
-
setCampaignDetail(data.campaign_detail);
|
|
3538
|
-
setFormData({
|
|
3539
|
-
detail_content: data.campaign_detail.detail_content || "",
|
|
3540
|
-
terms_and_conditions: data.campaign_detail.terms_and_conditions || "",
|
|
3541
|
-
meta_title: data.campaign_detail.meta_title || "",
|
|
3542
|
-
meta_description: data.campaign_detail.meta_description || "",
|
|
3543
|
-
meta_keywords: data.campaign_detail.meta_keywords || "",
|
|
3544
|
-
link_url: data.campaign_detail.link_url || "",
|
|
3545
|
-
link_text: data.campaign_detail.link_text || "",
|
|
3546
|
-
display_order: data.campaign_detail.display_order || 0
|
|
3547
|
-
});
|
|
3548
|
-
}
|
|
3549
|
-
} else if (response.status !== 404) {
|
|
3550
|
-
console.error("Failed to fetch campaign detail");
|
|
3551
|
-
}
|
|
3552
|
-
} catch (error) {
|
|
3553
|
-
console.error("Error fetching campaign detail:", error);
|
|
3554
|
-
} finally {
|
|
3555
|
-
setIsLoading(false);
|
|
3691
|
+
const { fields, append, remove } = useFieldArray({
|
|
3692
|
+
control,
|
|
3693
|
+
name: "products"
|
|
3694
|
+
});
|
|
3695
|
+
const [openProductModal, setOpenProductModal] = useState(false);
|
|
3696
|
+
const startsAt = watch("starts_at");
|
|
3697
|
+
const endsAt = watch("ends_at");
|
|
3698
|
+
const handleDateTimeChange = (field, type, value) => {
|
|
3699
|
+
const currentValue = watch(field);
|
|
3700
|
+
if (type === "date") {
|
|
3701
|
+
const time = currentValue ? dayjs(currentValue).format("HH:mm") : field === "starts_at" ? "00:00" : "23:59";
|
|
3702
|
+
setValue(field, `${value}T${time}`);
|
|
3703
|
+
} else {
|
|
3704
|
+
const date = currentValue ? dayjs(currentValue).format("YYYY-MM-DD") : dayjs().format("YYYY-MM-DD");
|
|
3705
|
+
setValue(field, `${date}T${value}`);
|
|
3556
3706
|
}
|
|
3557
3707
|
};
|
|
3558
|
-
const
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
});
|
|
3569
|
-
if (response.ok) {
|
|
3570
|
-
const data = await response.json();
|
|
3571
|
-
setCampaignDetail(data.campaign_detail);
|
|
3572
|
-
toast.success("Campaign details saved successfully");
|
|
3573
|
-
} else {
|
|
3574
|
-
const errorData = await response.json();
|
|
3575
|
-
toast.error(errorData.error || "Failed to save campaign details");
|
|
3708
|
+
const onFormSubmit = (data) => {
|
|
3709
|
+
onSubmit(data);
|
|
3710
|
+
};
|
|
3711
|
+
const onFormError = () => {
|
|
3712
|
+
const errorMessages = Object.entries(errors).map(([key, value]) => {
|
|
3713
|
+
var _a2;
|
|
3714
|
+
if (key === "products" && Array.isArray(value)) {
|
|
3715
|
+
return value.map(
|
|
3716
|
+
(item, index) => item ? `Product ${index + 1}: ${Object.values(item).join(", ")}` : ""
|
|
3717
|
+
).filter(Boolean).join(", ");
|
|
3576
3718
|
}
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
}
|
|
3719
|
+
return (value == null ? void 0 : value.message) || ((_a2 = value == null ? void 0 : value.root) == null ? void 0 : _a2.message);
|
|
3720
|
+
}).filter(Boolean).join(", ");
|
|
3721
|
+
toast.error("Invalid data", {
|
|
3722
|
+
description: errorMessages
|
|
3723
|
+
});
|
|
3583
3724
|
};
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
/* @__PURE__ */ jsx(
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
}
|
|
3622
|
-
}
|
|
3623
|
-
),
|
|
3624
|
-
/* @__PURE__ */ jsxs(
|
|
3625
|
-
Button,
|
|
3626
|
-
{
|
|
3627
|
-
variant: "secondary",
|
|
3628
|
-
size: "small",
|
|
3629
|
-
className: "absolute bottom-2 right-2",
|
|
3630
|
-
onClick: () => setShowImageUploader("image"),
|
|
3631
|
-
children: [
|
|
3632
|
-
/* @__PURE__ */ jsx(PencilSquare, { className: "mr-1" }),
|
|
3633
|
-
"Change"
|
|
3634
|
-
]
|
|
3635
|
-
}
|
|
3636
|
-
)
|
|
3637
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
3638
|
-
Button,
|
|
3725
|
+
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
3726
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3727
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "Flash sale campaign" }),
|
|
3728
|
+
/* @__PURE__ */ jsx(Button, { variant: "transparent", onClick: onCancel, children: "Cancel" })
|
|
3729
|
+
] }),
|
|
3730
|
+
/* @__PURE__ */ jsxs(
|
|
3731
|
+
"form",
|
|
3732
|
+
{
|
|
3733
|
+
onSubmit: handleSubmit(onFormSubmit, onFormError),
|
|
3734
|
+
className: "space-y-4 my-8",
|
|
3735
|
+
children: [
|
|
3736
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3737
|
+
/* @__PURE__ */ jsx(Label, { children: "Name" }),
|
|
3738
|
+
/* @__PURE__ */ jsx(Input, { ...register("name"), disabled }),
|
|
3739
|
+
errors.name && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.name.message })
|
|
3740
|
+
] }),
|
|
3741
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3742
|
+
/* @__PURE__ */ jsx(Label, { children: "Description" }),
|
|
3743
|
+
/* @__PURE__ */ jsx(Textarea, { ...register("description"), disabled }),
|
|
3744
|
+
errors.description && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
|
|
3745
|
+
] }),
|
|
3746
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
3747
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3748
|
+
/* @__PURE__ */ jsx(Label, { children: "Start Date" }),
|
|
3749
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3750
|
+
/* @__PURE__ */ jsx(
|
|
3751
|
+
Input,
|
|
3752
|
+
{
|
|
3753
|
+
type: "date",
|
|
3754
|
+
value: startsAt ? dayjs(startsAt).format("YYYY-MM-DD") : "",
|
|
3755
|
+
onChange: (e2) => handleDateTimeChange("starts_at", "date", e2.target.value),
|
|
3756
|
+
disabled,
|
|
3757
|
+
className: "flex-1"
|
|
3758
|
+
}
|
|
3759
|
+
),
|
|
3760
|
+
/* @__PURE__ */ jsx(
|
|
3761
|
+
Input,
|
|
3639
3762
|
{
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
]
|
|
3763
|
+
type: "time",
|
|
3764
|
+
value: startsAt ? dayjs(startsAt).format("HH:mm") : "",
|
|
3765
|
+
onChange: (e2) => handleDateTimeChange("starts_at", "time", e2.target.value),
|
|
3766
|
+
disabled,
|
|
3767
|
+
className: "w-32"
|
|
3646
3768
|
}
|
|
3647
3769
|
)
|
|
3648
|
-
}
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3770
|
+
] }),
|
|
3771
|
+
errors.starts_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
|
|
3772
|
+
] }),
|
|
3773
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3774
|
+
/* @__PURE__ */ jsx(Label, { children: "End Date" }),
|
|
3775
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3776
|
+
/* @__PURE__ */ jsx(
|
|
3777
|
+
Input,
|
|
3778
|
+
{
|
|
3779
|
+
type: "date",
|
|
3780
|
+
value: endsAt ? dayjs(endsAt).format("YYYY-MM-DD") : "",
|
|
3781
|
+
onChange: (e2) => handleDateTimeChange("ends_at", "date", e2.target.value),
|
|
3782
|
+
disabled,
|
|
3783
|
+
className: "flex-1"
|
|
3784
|
+
}
|
|
3659
3785
|
),
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
"img",
|
|
3663
|
-
{
|
|
3664
|
-
src: campaignDetail.thumbnail_url,
|
|
3665
|
-
alt: "Thumbnail",
|
|
3666
|
-
className: "h-full w-full object-cover",
|
|
3667
|
-
onError: (e2) => {
|
|
3668
|
-
console.error(
|
|
3669
|
-
"Failed to load thumbnail:",
|
|
3670
|
-
campaignDetail.thumbnail_url
|
|
3671
|
-
);
|
|
3672
|
-
e2.currentTarget.style.display = "none";
|
|
3673
|
-
}
|
|
3674
|
-
}
|
|
3675
|
-
),
|
|
3676
|
-
/* @__PURE__ */ jsxs(
|
|
3677
|
-
Button,
|
|
3678
|
-
{
|
|
3679
|
-
variant: "secondary",
|
|
3680
|
-
size: "small",
|
|
3681
|
-
className: "absolute bottom-2 right-2",
|
|
3682
|
-
onClick: () => setShowImageUploader("thumbnail"),
|
|
3683
|
-
children: [
|
|
3684
|
-
/* @__PURE__ */ jsx(PencilSquare, { className: "mr-1" }),
|
|
3685
|
-
"Change"
|
|
3686
|
-
]
|
|
3687
|
-
}
|
|
3688
|
-
)
|
|
3689
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
3690
|
-
Button,
|
|
3786
|
+
/* @__PURE__ */ jsx(
|
|
3787
|
+
Input,
|
|
3691
3788
|
{
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
]
|
|
3789
|
+
type: "time",
|
|
3790
|
+
value: endsAt ? dayjs(endsAt).format("HH:mm") : "",
|
|
3791
|
+
onChange: (e2) => handleDateTimeChange("ends_at", "time", e2.target.value),
|
|
3792
|
+
disabled,
|
|
3793
|
+
className: "w-32"
|
|
3698
3794
|
}
|
|
3699
3795
|
)
|
|
3700
|
-
}
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
] })
|
|
3704
|
-
] }),
|
|
3705
|
-
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
3706
|
-
MarkdownEditor,
|
|
3707
|
-
{
|
|
3708
|
-
label: "Campaign Detail Content",
|
|
3709
|
-
value: formData.detail_content,
|
|
3710
|
-
onChange: (value) => setFormData({ ...formData, detail_content: value }),
|
|
3711
|
-
placeholder: "Enter campaign details in Markdown format. You can include images, links, code blocks, and more...",
|
|
3712
|
-
helpText: "Supports Markdown and code syntax",
|
|
3713
|
-
rows: 12,
|
|
3714
|
-
showPreview: true
|
|
3715
|
-
}
|
|
3716
|
-
) }),
|
|
3717
|
-
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
3718
|
-
MarkdownEditor,
|
|
3719
|
-
{
|
|
3720
|
-
label: "Terms and Conditions",
|
|
3721
|
-
value: formData.terms_and_conditions,
|
|
3722
|
-
onChange: (value) => setFormData({ ...formData, terms_and_conditions: value }),
|
|
3723
|
-
placeholder: "Enter terms and conditions for this campaign...",
|
|
3724
|
-
helpText: "Optional - Campaign specific terms",
|
|
3725
|
-
rows: 8,
|
|
3726
|
-
showPreview: true
|
|
3727
|
-
}
|
|
3728
|
-
) }),
|
|
3729
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3730
|
-
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "Call to Action Link" }),
|
|
3731
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
3732
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3733
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "link-url", children: "Link URL" }),
|
|
3734
|
-
/* @__PURE__ */ jsx(
|
|
3735
|
-
Input,
|
|
3736
|
-
{
|
|
3737
|
-
id: "link-url",
|
|
3738
|
-
type: "url",
|
|
3739
|
-
value: formData.link_url,
|
|
3740
|
-
onChange: (e2) => setFormData({ ...formData, link_url: e2.target.value }),
|
|
3741
|
-
placeholder: "https://example.com/campaign"
|
|
3742
|
-
}
|
|
3743
|
-
)
|
|
3744
|
-
] }),
|
|
3745
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3746
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "link-text", children: "Link Text" }),
|
|
3747
|
-
/* @__PURE__ */ jsx(
|
|
3748
|
-
Input,
|
|
3749
|
-
{
|
|
3750
|
-
id: "link-text",
|
|
3751
|
-
value: formData.link_text,
|
|
3752
|
-
onChange: (e2) => setFormData({ ...formData, link_text: e2.target.value }),
|
|
3753
|
-
placeholder: "Shop Now"
|
|
3754
|
-
}
|
|
3755
|
-
)
|
|
3756
|
-
] })
|
|
3757
|
-
] })
|
|
3758
|
-
] }),
|
|
3759
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3760
|
-
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "SEO & Metadata" }),
|
|
3761
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3762
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3763
|
-
/* @__PURE__ */ jsx(Label, { htmlFor: "meta-title", children: "Meta Title" }),
|
|
3764
|
-
/* @__PURE__ */ jsx(
|
|
3765
|
-
Input,
|
|
3766
|
-
{
|
|
3767
|
-
id: "meta-title",
|
|
3768
|
-
value: formData.meta_title,
|
|
3769
|
-
onChange: (e2) => setFormData({ ...formData, meta_title: e2.target.value }),
|
|
3770
|
-
placeholder: "Campaign meta title for SEO"
|
|
3771
|
-
}
|
|
3772
|
-
)
|
|
3796
|
+
] }),
|
|
3797
|
+
errors.ends_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
|
|
3798
|
+
] })
|
|
3773
3799
|
] }),
|
|
3774
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
3775
|
-
/* @__PURE__ */ jsx(Label, {
|
|
3800
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
3801
|
+
/* @__PURE__ */ jsx(Label, { children: "Products" }),
|
|
3776
3802
|
/* @__PURE__ */ jsx(
|
|
3777
|
-
|
|
3803
|
+
Button,
|
|
3778
3804
|
{
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
}),
|
|
3785
|
-
placeholder: "Campaign meta description for SEO"
|
|
3805
|
+
type: "button",
|
|
3806
|
+
variant: "secondary",
|
|
3807
|
+
onClick: () => setOpenProductModal(true),
|
|
3808
|
+
disabled,
|
|
3809
|
+
children: "Add Product"
|
|
3786
3810
|
}
|
|
3787
3811
|
)
|
|
3788
3812
|
] }),
|
|
3789
|
-
/* @__PURE__ */
|
|
3790
|
-
|
|
3791
|
-
/* @__PURE__ */ jsx(
|
|
3792
|
-
|
|
3793
|
-
{
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3813
|
+
((_a = errors.products) == null ? void 0 : _a.root) && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm", children: errors.products.root.message }),
|
|
3814
|
+
/* @__PURE__ */ jsxs(Table, { children: [
|
|
3815
|
+
/* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
3816
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Product" }),
|
|
3817
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Discount Type" }),
|
|
3818
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Discount Value" }),
|
|
3819
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Limit" }),
|
|
3820
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Max Qty per Order" }),
|
|
3821
|
+
/* @__PURE__ */ jsx(Table.HeaderCell, { children: "Actions" })
|
|
3822
|
+
] }) }),
|
|
3823
|
+
/* @__PURE__ */ jsx(Table.Body, { children: fields.map((field, index) => /* @__PURE__ */ jsxs(Table.Row, { children: [
|
|
3824
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: field.product.title }),
|
|
3825
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
3826
|
+
Controller,
|
|
3827
|
+
{
|
|
3828
|
+
name: `products.${index}.discountType`,
|
|
3829
|
+
control,
|
|
3830
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsxs(
|
|
3831
|
+
Select,
|
|
3832
|
+
{
|
|
3833
|
+
value: field2.value,
|
|
3834
|
+
onValueChange: field2.onChange,
|
|
3835
|
+
disabled,
|
|
3836
|
+
children: [
|
|
3837
|
+
/* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select discount type" }) }),
|
|
3838
|
+
/* @__PURE__ */ jsx(Select.Content, { children: /* @__PURE__ */ jsx(Select.Item, { value: "percentage", children: "Percentage" }) })
|
|
3839
|
+
]
|
|
3840
|
+
}
|
|
3841
|
+
)
|
|
3842
|
+
}
|
|
3843
|
+
) }),
|
|
3844
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
3845
|
+
Controller,
|
|
3846
|
+
{
|
|
3847
|
+
name: `products.${index}.discountValue`,
|
|
3848
|
+
control,
|
|
3849
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsx(
|
|
3850
|
+
Input,
|
|
3851
|
+
{
|
|
3852
|
+
type: "number",
|
|
3853
|
+
value: field2.value,
|
|
3854
|
+
onChange: (e2) => field2.onChange(Number(e2.target.value)),
|
|
3855
|
+
disabled
|
|
3856
|
+
}
|
|
3857
|
+
)
|
|
3858
|
+
}
|
|
3859
|
+
) }),
|
|
3860
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
3861
|
+
Controller,
|
|
3862
|
+
{
|
|
3863
|
+
name: `products.${index}.limit`,
|
|
3864
|
+
control,
|
|
3865
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsx(
|
|
3866
|
+
Input,
|
|
3867
|
+
{
|
|
3868
|
+
type: "number",
|
|
3869
|
+
value: field2.value,
|
|
3870
|
+
onChange: (e2) => field2.onChange(Number(e2.target.value)),
|
|
3871
|
+
disabled
|
|
3872
|
+
}
|
|
3873
|
+
)
|
|
3874
|
+
}
|
|
3875
|
+
) }),
|
|
3876
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
3877
|
+
Controller,
|
|
3878
|
+
{
|
|
3879
|
+
name: `products.${index}.maxQty`,
|
|
3880
|
+
control,
|
|
3881
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsx(
|
|
3882
|
+
Input,
|
|
3883
|
+
{
|
|
3884
|
+
type: "number",
|
|
3885
|
+
value: field2.value,
|
|
3886
|
+
onChange: (e2) => field2.onChange(Number(e2.target.value)),
|
|
3887
|
+
disabled
|
|
3888
|
+
}
|
|
3889
|
+
)
|
|
3890
|
+
}
|
|
3891
|
+
) }),
|
|
3892
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
3893
|
+
Button,
|
|
3894
|
+
{
|
|
3895
|
+
type: "button",
|
|
3896
|
+
variant: "danger",
|
|
3897
|
+
onClick: () => remove(index),
|
|
3898
|
+
disabled,
|
|
3899
|
+
children: /* @__PURE__ */ jsx(Trash, {})
|
|
3900
|
+
}
|
|
3901
|
+
) })
|
|
3902
|
+
] }, field.id)) })
|
|
3804
3903
|
] }),
|
|
3805
|
-
/* @__PURE__ */
|
|
3806
|
-
|
|
3807
|
-
/* @__PURE__ */ jsx(
|
|
3808
|
-
Input,
|
|
3809
|
-
{
|
|
3810
|
-
id: "display-order",
|
|
3811
|
-
type: "number",
|
|
3812
|
-
value: formData.display_order,
|
|
3813
|
-
onChange: (e2) => setFormData({
|
|
3814
|
-
...formData,
|
|
3815
|
-
display_order: parseInt(e2.target.value) || 0
|
|
3816
|
-
}),
|
|
3817
|
-
placeholder: "0"
|
|
3818
|
-
}
|
|
3819
|
-
),
|
|
3820
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Lower numbers appear first in listings" })
|
|
3821
|
-
] })
|
|
3822
|
-
] })
|
|
3823
|
-
] }),
|
|
3824
|
-
/* @__PURE__ */ jsx("div", { className: "flex justify-end border-t pt-4", children: /* @__PURE__ */ jsx(Button, { onClick: handleSave, isLoading: isSaving, children: "Save Campaign Details" }) })
|
|
3825
|
-
] }),
|
|
3826
|
-
showImageUploader && /* @__PURE__ */ jsx(
|
|
3827
|
-
CampaignImageUploader,
|
|
3828
|
-
{
|
|
3829
|
-
campaignId,
|
|
3830
|
-
imageType: showImageUploader,
|
|
3831
|
-
currentImageUrl: showImageUploader === "image" ? campaignDetail == null ? void 0 : campaignDetail.image_url : campaignDetail == null ? void 0 : campaignDetail.thumbnail_url,
|
|
3832
|
-
onClose: () => setShowImageUploader(null),
|
|
3833
|
-
onSuccess: fetchCampaignDetail
|
|
3834
|
-
}
|
|
3835
|
-
)
|
|
3836
|
-
] });
|
|
3837
|
-
};
|
|
3838
|
-
const CouponDetail = () => {
|
|
3839
|
-
var _a, _b, _c, _d, _e, _f;
|
|
3840
|
-
const { id } = useParams();
|
|
3841
|
-
const navigate = useNavigate();
|
|
3842
|
-
const [isEditing, setIsEditing] = useState(false);
|
|
3843
|
-
const { data, isLoading, refetch } = useCouponById(id ?? "");
|
|
3844
|
-
if (isLoading) {
|
|
3845
|
-
return /* @__PURE__ */ jsx(Container, { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx("div", { className: "animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent" }) });
|
|
3846
|
-
}
|
|
3847
|
-
if (!(data == null ? void 0 : data.campaign)) {
|
|
3848
|
-
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("p", { children: "Coupon not found" }) });
|
|
3849
|
-
}
|
|
3850
|
-
const campaign = data.campaign;
|
|
3851
|
-
const promotion = (_a = campaign.promotions) == null ? void 0 : _a[0];
|
|
3852
|
-
const initialValues = {
|
|
3853
|
-
name: campaign.name ?? "",
|
|
3854
|
-
description: campaign.description ?? "",
|
|
3855
|
-
type: "coupon",
|
|
3856
|
-
code: (promotion == null ? void 0 : promotion.code) ?? "",
|
|
3857
|
-
discount_type: ((_b = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _b.type) ?? "percentage",
|
|
3858
|
-
discount_value: Number(((_c = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _c.value) ?? 0),
|
|
3859
|
-
currency_code: ((_d = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _d.currency_code) ?? void 0,
|
|
3860
|
-
starts_at: campaign.starts_at ? dayjs(campaign.starts_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
3861
|
-
ends_at: campaign.ends_at ? dayjs(campaign.ends_at).format("YYYY-MM-DDTHH:mm") : void 0,
|
|
3862
|
-
allocation: ((_e = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _e.allocation) ?? "total",
|
|
3863
|
-
target_type: ((_f = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _f.target_type) ?? "order"
|
|
3864
|
-
};
|
|
3865
|
-
const handleSubmit = async (formData) => {
|
|
3866
|
-
var _a2, _b2;
|
|
3867
|
-
if (!id) {
|
|
3868
|
-
return;
|
|
3869
|
-
}
|
|
3870
|
-
try {
|
|
3871
|
-
await axios.put(`/admin/coupons/${id}`, {
|
|
3872
|
-
...formData,
|
|
3873
|
-
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
3874
|
-
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
3875
|
-
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
3876
|
-
});
|
|
3877
|
-
toast.success("Coupon updated successfully");
|
|
3878
|
-
setIsEditing(false);
|
|
3879
|
-
refetch();
|
|
3880
|
-
} catch (error) {
|
|
3881
|
-
let message = "Failed to update coupon";
|
|
3882
|
-
if (axios.isAxiosError(error)) {
|
|
3883
|
-
message = ((_b2 = (_a2 = error.response) == null ? void 0 : _a2.data) == null ? void 0 : _b2.message) ?? message;
|
|
3884
|
-
} else if (error instanceof Error) {
|
|
3885
|
-
message = error.message;
|
|
3904
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", disabled, children: "Save" })
|
|
3905
|
+
]
|
|
3886
3906
|
}
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
})
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
3893
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
3894
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
3895
|
-
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold mb-1", children: campaign.name }),
|
|
3896
|
-
/* @__PURE__ */ jsx("p", { className: "text-ui-fg-subtle", children: "Manage coupon configuration and content" })
|
|
3897
|
-
] }),
|
|
3898
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3899
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/coupons"), children: "Back to Coupons" }),
|
|
3900
|
-
/* @__PURE__ */ jsx(Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
|
|
3901
|
-
] })
|
|
3902
|
-
] }),
|
|
3903
|
-
/* @__PURE__ */ jsxs(Tabs, { defaultValue: "settings", children: [
|
|
3904
|
-
/* @__PURE__ */ jsxs(Tabs.List, { children: [
|
|
3905
|
-
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "settings", children: "Coupon Settings" }),
|
|
3906
|
-
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "details", children: "Coupon Details" })
|
|
3907
|
-
] }),
|
|
3908
|
-
/* @__PURE__ */ jsx(Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
3909
|
-
CouponForm,
|
|
3910
|
-
{
|
|
3911
|
-
initialData: initialValues,
|
|
3912
|
-
onSubmit: handleSubmit,
|
|
3913
|
-
onCancel: () => navigate("/coupons"),
|
|
3914
|
-
disabled: !isEditing
|
|
3915
|
-
}
|
|
3916
|
-
) }),
|
|
3917
|
-
/* @__PURE__ */ jsx(Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
3918
|
-
CampaignDetailForm,
|
|
3907
|
+
),
|
|
3908
|
+
/* @__PURE__ */ jsx(FocusModal, { open: openProductModal, onOpenChange: setOpenProductModal, children: /* @__PURE__ */ jsxs(FocusModal.Content, { children: [
|
|
3909
|
+
/* @__PURE__ */ jsx(FocusModal.Header, { title: "Add Product" }),
|
|
3910
|
+
/* @__PURE__ */ jsx(FocusModal.Body, { children: /* @__PURE__ */ jsx(
|
|
3911
|
+
ProductSelector,
|
|
3919
3912
|
{
|
|
3920
|
-
|
|
3921
|
-
|
|
3913
|
+
selectedProductIds: fields.map((f2) => f2.product.id),
|
|
3914
|
+
onSelectProduct: (product) => {
|
|
3915
|
+
append({
|
|
3916
|
+
product,
|
|
3917
|
+
discountType: "percentage",
|
|
3918
|
+
discountValue: 10,
|
|
3919
|
+
limit: 10,
|
|
3920
|
+
maxQty: 1
|
|
3921
|
+
});
|
|
3922
|
+
setOpenProductModal(false);
|
|
3923
|
+
}
|
|
3922
3924
|
}
|
|
3923
3925
|
) })
|
|
3924
|
-
] })
|
|
3926
|
+
] }) })
|
|
3925
3927
|
] });
|
|
3926
3928
|
};
|
|
3927
|
-
const config$1 = defineRouteConfig({
|
|
3928
|
-
label: "Coupon Detail",
|
|
3929
|
-
icon: Tag
|
|
3930
|
-
});
|
|
3931
|
-
const CouponCreate = () => {
|
|
3932
|
-
const navigate = useNavigate();
|
|
3933
|
-
const handleSubmit = async (formData) => {
|
|
3934
|
-
var _a, _b;
|
|
3935
|
-
try {
|
|
3936
|
-
await axios.post("/admin/coupons", {
|
|
3937
|
-
...formData,
|
|
3938
|
-
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
3939
|
-
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
3940
|
-
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
3941
|
-
});
|
|
3942
|
-
toast.success("Coupon created successfully");
|
|
3943
|
-
navigate("/coupons");
|
|
3944
|
-
} catch (error) {
|
|
3945
|
-
let message = "Failed to create coupon";
|
|
3946
|
-
if (axios.isAxiosError(error)) {
|
|
3947
|
-
message = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) ?? message;
|
|
3948
|
-
} else if (error instanceof Error) {
|
|
3949
|
-
message = error.message;
|
|
3950
|
-
}
|
|
3951
|
-
toast.error("Failed to create coupon", {
|
|
3952
|
-
description: message
|
|
3953
|
-
});
|
|
3954
|
-
}
|
|
3955
|
-
};
|
|
3956
|
-
return /* @__PURE__ */ jsx(
|
|
3957
|
-
CouponForm,
|
|
3958
|
-
{
|
|
3959
|
-
onSubmit: handleSubmit,
|
|
3960
|
-
onCancel: () => navigate("/coupons")
|
|
3961
|
-
}
|
|
3962
|
-
);
|
|
3963
|
-
};
|
|
3964
3929
|
const useFlashSaleById = (id) => {
|
|
3965
3930
|
const [data, setData] = useState(null);
|
|
3966
3931
|
const [isLoading, setIsLoading] = useState(true);
|
|
@@ -4079,6 +4044,41 @@ const config = defineRouteConfig({
|
|
|
4079
4044
|
label: "Flash Sale Detail",
|
|
4080
4045
|
icon: Sparkles
|
|
4081
4046
|
});
|
|
4047
|
+
const FlashSaleCreate = () => {
|
|
4048
|
+
const navigate = useNavigate();
|
|
4049
|
+
async function handleSubmit(campaignData) {
|
|
4050
|
+
var _a;
|
|
4051
|
+
try {
|
|
4052
|
+
await axios.post("/admin/flash-sales", {
|
|
4053
|
+
...campaignData,
|
|
4054
|
+
starts_at: new Date(campaignData.starts_at).toUTCString(),
|
|
4055
|
+
ends_at: new Date(campaignData.ends_at).toUTCString()
|
|
4056
|
+
});
|
|
4057
|
+
toast.success("Flash sale created successfully");
|
|
4058
|
+
navigate("/flash-sales");
|
|
4059
|
+
} catch (error) {
|
|
4060
|
+
let message;
|
|
4061
|
+
if (axios.isAxiosError(error)) {
|
|
4062
|
+
console.log(error);
|
|
4063
|
+
message = (_a = error.response) == null ? void 0 : _a.data.message;
|
|
4064
|
+
} else if (error instanceof Error) {
|
|
4065
|
+
message = error.message;
|
|
4066
|
+
} else {
|
|
4067
|
+
message = "Failed to create flash sale";
|
|
4068
|
+
}
|
|
4069
|
+
toast.error("Failed to create flash sale", {
|
|
4070
|
+
description: message
|
|
4071
|
+
});
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
return /* @__PURE__ */ jsx(
|
|
4075
|
+
FlashSaleForm,
|
|
4076
|
+
{
|
|
4077
|
+
onSubmit: handleSubmit,
|
|
4078
|
+
onCancel: () => navigate("/flash-sales")
|
|
4079
|
+
}
|
|
4080
|
+
);
|
|
4081
|
+
};
|
|
4082
4082
|
const widgetModule = { widgets: [
|
|
4083
4083
|
{
|
|
4084
4084
|
Component: CampaignDetailWidget,
|
|
@@ -4087,17 +4087,13 @@ const widgetModule = { widgets: [
|
|
|
4087
4087
|
] };
|
|
4088
4088
|
const routeModule = {
|
|
4089
4089
|
routes: [
|
|
4090
|
-
{
|
|
4091
|
-
Component: FlashSale,
|
|
4092
|
-
path: "/flash-sales"
|
|
4093
|
-
},
|
|
4094
4090
|
{
|
|
4095
4091
|
Component: Coupons,
|
|
4096
4092
|
path: "/coupons"
|
|
4097
4093
|
},
|
|
4098
4094
|
{
|
|
4099
|
-
Component:
|
|
4100
|
-
path: "/flash-sales
|
|
4095
|
+
Component: FlashSale,
|
|
4096
|
+
path: "/flash-sales"
|
|
4101
4097
|
},
|
|
4102
4098
|
{
|
|
4103
4099
|
Component: CouponDetail,
|
|
@@ -4110,20 +4106,24 @@ const routeModule = {
|
|
|
4110
4106
|
{
|
|
4111
4107
|
Component: FlashSaleDetail,
|
|
4112
4108
|
path: "/flash-sales/:id"
|
|
4109
|
+
},
|
|
4110
|
+
{
|
|
4111
|
+
Component: FlashSaleCreate,
|
|
4112
|
+
path: "/flash-sales/create"
|
|
4113
4113
|
}
|
|
4114
4114
|
]
|
|
4115
4115
|
};
|
|
4116
4116
|
const menuItemModule = {
|
|
4117
4117
|
menuItems: [
|
|
4118
4118
|
{
|
|
4119
|
-
label: config$
|
|
4120
|
-
icon: config$
|
|
4119
|
+
label: config$3.label,
|
|
4120
|
+
icon: config$3.icon,
|
|
4121
4121
|
path: "/coupons",
|
|
4122
4122
|
nested: void 0
|
|
4123
4123
|
},
|
|
4124
4124
|
{
|
|
4125
|
-
label: config$
|
|
4126
|
-
icon: config$
|
|
4125
|
+
label: config$2.label,
|
|
4126
|
+
icon: config$2.icon,
|
|
4127
4127
|
path: "/flash-sales",
|
|
4128
4128
|
nested: void 0
|
|
4129
4129
|
},
|