@lodashventure/medusa-campaign 1.4.2 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/admin/index.js +1578 -1578
- package/.medusa/server/src/admin/index.mjs +1580 -1580
- 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, InformationCircle, CommandLine, X, CloudArrowUp
|
|
3
|
+
import { Container, Heading, Text, Alert, Badge, Button, createDataTableColumnHelper, useDataTable, DataTable, Input, Label, Textarea, Table, Select, FocusModal, toast, Tabs, clx } from "@medusajs/ui";
|
|
4
|
+
import { Sparkles, PhotoSolid, PencilSquare, Eye, Tag, Trash, InformationCircle, CommandLine, X, CloudArrowUp } from "@medusajs/icons";
|
|
5
5
|
import React, { useState, useEffect, useMemo, useCallback } from "react";
|
|
6
6
|
import { useNavigate, useParams } from "react-router-dom";
|
|
7
7
|
import dayjs from "dayjs";
|
|
@@ -194,99 +194,102 @@ const CampaignDetailWidget = ({
|
|
|
194
194
|
defineWidgetConfig({
|
|
195
195
|
zone: "promotion.details.side.after"
|
|
196
196
|
});
|
|
197
|
-
const
|
|
197
|
+
const useFlashSales = (pagination) => {
|
|
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 fetchFlashSales = 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/flash-sales", {
|
|
206
|
+
params: pagination
|
|
207
207
|
});
|
|
208
208
|
setData(response.data);
|
|
209
209
|
} catch (err) {
|
|
210
|
-
setError(
|
|
210
|
+
setError(
|
|
211
|
+
err instanceof Error ? err : new Error("Failed to fetch flash sales")
|
|
212
|
+
);
|
|
211
213
|
} finally {
|
|
212
214
|
setIsLoading(false);
|
|
213
215
|
}
|
|
214
216
|
};
|
|
215
217
|
useEffect(() => {
|
|
216
|
-
|
|
217
|
-
}, [limit, offset]);
|
|
218
|
+
fetchFlashSales();
|
|
219
|
+
}, [pagination.limit, pagination.offset]);
|
|
218
220
|
return {
|
|
219
221
|
data,
|
|
220
222
|
isLoading,
|
|
221
223
|
error,
|
|
222
|
-
refetch:
|
|
224
|
+
refetch: fetchFlashSales
|
|
223
225
|
};
|
|
224
226
|
};
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
const FlashSalePage = () => {
|
|
228
|
+
const navigate = useNavigate();
|
|
229
|
+
const limit = 20;
|
|
230
|
+
const [pagination, setPagination] = useState({
|
|
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", {
|
|
229
240
|
header: "Name",
|
|
230
241
|
cell: (info) => info.getValue()
|
|
231
242
|
}),
|
|
232
|
-
|
|
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",
|
|
243
|
+
columnHelper2.accessor("ends_at", {
|
|
242
244
|
header: "Status",
|
|
243
245
|
cell: (info) => {
|
|
244
|
-
const
|
|
245
|
-
if (!
|
|
246
|
+
const date = info.getValue();
|
|
247
|
+
if (!date) {
|
|
246
248
|
return "";
|
|
247
249
|
}
|
|
248
|
-
const isActive = dayjs(
|
|
250
|
+
const isActive = dayjs(date).isAfter(dayjs());
|
|
249
251
|
return /* @__PURE__ */ jsx(Badge, { color: isActive ? "green" : "grey", children: isActive ? "Active" : "Inactive" });
|
|
250
252
|
}
|
|
251
253
|
}),
|
|
252
|
-
|
|
254
|
+
columnHelper2.accessor("starts_at", {
|
|
253
255
|
header: "Start Date",
|
|
254
|
-
cell: (info) =>
|
|
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
|
+
}
|
|
255
263
|
}),
|
|
256
|
-
|
|
264
|
+
columnHelper2.accessor("ends_at", {
|
|
257
265
|
header: "End Date",
|
|
258
|
-
cell: (info) =>
|
|
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
|
+
}
|
|
259
273
|
})
|
|
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();
|
|
274
|
+
];
|
|
275
|
+
const { data } = useFlashSales({ limit, offset });
|
|
273
276
|
const table = useDataTable({
|
|
274
|
-
data: (data == null ? void 0 : data.campaigns)
|
|
277
|
+
data: (data == null ? void 0 : data.campaigns) || [],
|
|
275
278
|
columns: columns2,
|
|
276
|
-
getRowId: (
|
|
279
|
+
getRowId: (campaign) => campaign.id,
|
|
277
280
|
pagination: {
|
|
278
281
|
state: pagination,
|
|
279
282
|
onPaginationChange: setPagination
|
|
280
283
|
},
|
|
281
|
-
rowCount: (data == null ? void 0 : data.count)
|
|
284
|
+
rowCount: (data == null ? void 0 : data.count) || 0,
|
|
282
285
|
onRowClick: (_, row) => {
|
|
283
|
-
navigate(`/
|
|
286
|
+
navigate(`/flash-sales/${row.id}`);
|
|
284
287
|
}
|
|
285
288
|
});
|
|
286
289
|
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
287
290
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
288
|
-
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "
|
|
289
|
-
/* @__PURE__ */ jsx(Button, { onClick: () => navigate("/
|
|
291
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "Campaigns" }),
|
|
292
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => navigate("/flash-sales/create"), children: "Create Campaign" })
|
|
290
293
|
] }),
|
|
291
294
|
/* @__PURE__ */ jsxs(DataTable, { instance: table, children: [
|
|
292
295
|
/* @__PURE__ */ jsx(DataTable.Table, {}),
|
|
@@ -294,109 +297,106 @@ const CouponPage = () => {
|
|
|
294
297
|
] })
|
|
295
298
|
] });
|
|
296
299
|
};
|
|
297
|
-
const
|
|
298
|
-
return /* @__PURE__ */ jsx(
|
|
300
|
+
const FlashSale = () => {
|
|
301
|
+
return /* @__PURE__ */ jsx(FlashSalePage, {});
|
|
299
302
|
};
|
|
300
303
|
const config$3 = defineRouteConfig({
|
|
301
|
-
label: "
|
|
302
|
-
icon:
|
|
304
|
+
label: "Flash Sale",
|
|
305
|
+
icon: Sparkles
|
|
303
306
|
});
|
|
304
|
-
const
|
|
307
|
+
const useCoupons = ({ limit, offset }) => {
|
|
305
308
|
const [data, setData] = useState(null);
|
|
306
309
|
const [isLoading, setIsLoading] = useState(true);
|
|
307
310
|
const [error, setError] = useState(null);
|
|
308
|
-
const
|
|
311
|
+
const fetchCoupons = async () => {
|
|
309
312
|
setIsLoading(true);
|
|
310
313
|
setError(null);
|
|
311
314
|
try {
|
|
312
|
-
const response = await axios.get("/admin/
|
|
313
|
-
params:
|
|
315
|
+
const response = await axios.get("/admin/coupons", {
|
|
316
|
+
params: { limit, offset }
|
|
314
317
|
});
|
|
315
318
|
setData(response.data);
|
|
316
319
|
} catch (err) {
|
|
317
|
-
setError(
|
|
318
|
-
err instanceof Error ? err : new Error("Failed to fetch flash sales")
|
|
319
|
-
);
|
|
320
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch coupons"));
|
|
320
321
|
} finally {
|
|
321
322
|
setIsLoading(false);
|
|
322
323
|
}
|
|
323
324
|
};
|
|
324
325
|
useEffect(() => {
|
|
325
|
-
|
|
326
|
-
}, [
|
|
326
|
+
fetchCoupons();
|
|
327
|
+
}, [limit, offset]);
|
|
327
328
|
return {
|
|
328
329
|
data,
|
|
329
330
|
isLoading,
|
|
330
331
|
error,
|
|
331
|
-
refetch:
|
|
332
|
+
refetch: fetchCoupons
|
|
332
333
|
};
|
|
333
334
|
};
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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", {
|
|
335
|
+
const columnHelper$1 = createDataTableColumnHelper();
|
|
336
|
+
const useCouponColumns = () => useMemo(
|
|
337
|
+
() => [
|
|
338
|
+
columnHelper$1.accessor("name", {
|
|
347
339
|
header: "Name",
|
|
348
340
|
cell: (info) => info.getValue()
|
|
349
341
|
}),
|
|
350
|
-
|
|
342
|
+
columnHelper$1.display({
|
|
343
|
+
id: "code",
|
|
344
|
+
header: "Code",
|
|
345
|
+
cell: (info) => {
|
|
346
|
+
var _a, _b;
|
|
347
|
+
return ((_b = (_a = info.row.original.promotions) == null ? void 0 : _a[0]) == null ? void 0 : _b.code) ?? "-";
|
|
348
|
+
}
|
|
349
|
+
}),
|
|
350
|
+
columnHelper$1.display({
|
|
351
|
+
id: "status",
|
|
351
352
|
header: "Status",
|
|
352
353
|
cell: (info) => {
|
|
353
|
-
const
|
|
354
|
-
if (!
|
|
354
|
+
const endsAt = info.row.original.ends_at;
|
|
355
|
+
if (!endsAt) {
|
|
355
356
|
return "";
|
|
356
357
|
}
|
|
357
|
-
const isActive = dayjs(
|
|
358
|
+
const isActive = dayjs(endsAt).isAfter(dayjs());
|
|
358
359
|
return /* @__PURE__ */ jsx(Badge, { color: isActive ? "green" : "grey", children: isActive ? "Active" : "Inactive" });
|
|
359
360
|
}
|
|
360
361
|
}),
|
|
361
|
-
|
|
362
|
+
columnHelper$1.accessor("starts_at", {
|
|
362
363
|
header: "Start Date",
|
|
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
|
-
}
|
|
364
|
+
cell: (info) => info.getValue() ? dayjs(info.getValue()).format("YYYY-MM-DD HH:mm") : ""
|
|
370
365
|
}),
|
|
371
|
-
|
|
366
|
+
columnHelper$1.accessor("ends_at", {
|
|
372
367
|
header: "End Date",
|
|
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
|
-
}
|
|
368
|
+
cell: (info) => info.getValue() ? dayjs(info.getValue()).format("YYYY-MM-DD HH:mm") : ""
|
|
380
369
|
})
|
|
381
|
-
]
|
|
382
|
-
|
|
370
|
+
],
|
|
371
|
+
[]
|
|
372
|
+
);
|
|
373
|
+
const CouponPage = () => {
|
|
374
|
+
const navigate = useNavigate();
|
|
375
|
+
const limit = 20;
|
|
376
|
+
const [pagination, setPagination] = useState({
|
|
377
|
+
pageSize: limit,
|
|
378
|
+
pageIndex: 0
|
|
379
|
+
});
|
|
380
|
+
const offset = pagination.pageIndex * limit;
|
|
381
|
+
const { data } = useCoupons({ limit, offset });
|
|
382
|
+
const columns2 = useCouponColumns();
|
|
383
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: (row) => row.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(`/coupons/${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: "Coupons" }),
|
|
399
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => navigate("/coupons/create"), children: "Create Coupon" })
|
|
400
400
|
] }),
|
|
401
401
|
/* @__PURE__ */ jsxs(DataTable, { instance: table, children: [
|
|
402
402
|
/* @__PURE__ */ jsx(DataTable.Table, {}),
|
|
@@ -404,12 +404,12 @@ const FlashSalePage = () => {
|
|
|
404
404
|
] })
|
|
405
405
|
] });
|
|
406
406
|
};
|
|
407
|
-
const
|
|
408
|
-
return /* @__PURE__ */ jsx(
|
|
407
|
+
const Coupons = () => {
|
|
408
|
+
return /* @__PURE__ */ jsx(CouponPage, {});
|
|
409
409
|
};
|
|
410
410
|
const config$2 = defineRouteConfig({
|
|
411
|
-
label: "
|
|
412
|
-
icon:
|
|
411
|
+
label: "Coupons",
|
|
412
|
+
icon: Tag
|
|
413
413
|
});
|
|
414
414
|
var isCheckBoxInput = (element) => element.type === "checkbox";
|
|
415
415
|
var isDateObject = (value) => value instanceof Date;
|
|
@@ -2292,64 +2292,134 @@ var n = function(e2, o2) {
|
|
|
2292
2292
|
}
|
|
2293
2293
|
};
|
|
2294
2294
|
};
|
|
2295
|
-
const
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
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
|
-
});
|
|
2295
|
+
const sdk = new Medusa({
|
|
2296
|
+
baseUrl: "/",
|
|
2297
|
+
debug: false,
|
|
2298
|
+
auth: {
|
|
2299
|
+
type: "session"
|
|
2321
2300
|
}
|
|
2322
2301
|
});
|
|
2323
|
-
const
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
})
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2302
|
+
const columnHelper = createDataTableColumnHelper();
|
|
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 = ({
|
|
2393
|
+
initialData,
|
|
2394
|
+
initialProducts,
|
|
2395
|
+
onSubmit,
|
|
2396
|
+
onCancel,
|
|
2397
|
+
disabled = false
|
|
2398
|
+
}) => {
|
|
2399
|
+
var _a;
|
|
2400
|
+
const {
|
|
2401
|
+
control,
|
|
2402
|
+
register,
|
|
2403
|
+
handleSubmit,
|
|
2404
|
+
watch,
|
|
2334
2405
|
setValue,
|
|
2335
2406
|
formState: { errors }
|
|
2336
2407
|
} = useForm({
|
|
2337
|
-
resolver: t(
|
|
2408
|
+
resolver: t(campaignDataSchema),
|
|
2338
2409
|
defaultValues: {
|
|
2339
|
-
name: (initialData == null ? void 0 : initialData.name)
|
|
2340
|
-
description: (initialData == null ? void 0 : initialData.description)
|
|
2341
|
-
type: "
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
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"
|
|
2410
|
+
name: (initialData == null ? void 0 : initialData.name) || "",
|
|
2411
|
+
description: (initialData == null ? void 0 : initialData.description) || "",
|
|
2412
|
+
type: "flash-sale",
|
|
2413
|
+
starts_at: (initialData == null ? void 0 : initialData.starts_at) || "",
|
|
2414
|
+
ends_at: (initialData == null ? void 0 : initialData.ends_at) || "",
|
|
2415
|
+
products: initialProducts ? Array.from(initialProducts.values()) : []
|
|
2350
2416
|
}
|
|
2351
2417
|
});
|
|
2352
|
-
const
|
|
2418
|
+
const { fields, append, remove } = useFieldArray({
|
|
2419
|
+
control,
|
|
2420
|
+
name: "products"
|
|
2421
|
+
});
|
|
2422
|
+
const [openProductModal, setOpenProductModal] = useState(false);
|
|
2353
2423
|
const startsAt = watch("starts_at");
|
|
2354
2424
|
const endsAt = watch("ends_at");
|
|
2355
2425
|
const handleDateTimeChange = (field, type, value) => {
|
|
@@ -2366,34 +2436,42 @@ const CouponForm = ({
|
|
|
2366
2436
|
onSubmit(data);
|
|
2367
2437
|
};
|
|
2368
2438
|
const onFormError = () => {
|
|
2369
|
-
const errorMessages = Object.
|
|
2370
|
-
|
|
2439
|
+
const errorMessages = Object.entries(errors).map(([key, value]) => {
|
|
2440
|
+
var _a2;
|
|
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", {
|
|
2371
2449
|
description: errorMessages
|
|
2372
2450
|
});
|
|
2373
2451
|
};
|
|
2374
2452
|
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
2375
2453
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2376
|
-
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "
|
|
2454
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "Flash sale campaign" }),
|
|
2377
2455
|
/* @__PURE__ */ jsx(Button, { variant: "transparent", onClick: onCancel, children: "Cancel" })
|
|
2378
2456
|
] }),
|
|
2379
2457
|
/* @__PURE__ */ jsxs(
|
|
2380
2458
|
"form",
|
|
2381
2459
|
{
|
|
2382
2460
|
onSubmit: handleSubmit(onFormSubmit, onFormError),
|
|
2383
|
-
className: "space-y-
|
|
2461
|
+
className: "space-y-4 my-8",
|
|
2384
2462
|
children: [
|
|
2385
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2463
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2386
2464
|
/* @__PURE__ */ jsx(Label, { children: "Name" }),
|
|
2387
2465
|
/* @__PURE__ */ jsx(Input, { ...register("name"), disabled }),
|
|
2388
2466
|
errors.name && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.name.message })
|
|
2389
2467
|
] }),
|
|
2390
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2468
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2391
2469
|
/* @__PURE__ */ jsx(Label, { children: "Description" }),
|
|
2392
2470
|
/* @__PURE__ */ jsx(Textarea, { ...register("description"), disabled }),
|
|
2393
2471
|
errors.description && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
|
|
2394
2472
|
] }),
|
|
2395
2473
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2396
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2474
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2397
2475
|
/* @__PURE__ */ jsx(Label, { children: "Start Date" }),
|
|
2398
2476
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2399
2477
|
/* @__PURE__ */ jsx(
|
|
@@ -2413,13 +2491,13 @@ const CouponForm = ({
|
|
|
2413
2491
|
value: startsAt ? dayjs(startsAt).format("HH:mm") : "",
|
|
2414
2492
|
onChange: (e2) => handleDateTimeChange("starts_at", "time", e2.target.value),
|
|
2415
2493
|
disabled,
|
|
2416
|
-
className: "
|
|
2494
|
+
className: "w-32"
|
|
2417
2495
|
}
|
|
2418
2496
|
)
|
|
2419
2497
|
] }),
|
|
2420
2498
|
errors.starts_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
|
|
2421
2499
|
] }),
|
|
2422
|
-
/* @__PURE__ */ jsxs("div", {
|
|
2500
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2423
2501
|
/* @__PURE__ */ jsx(Label, { children: "End Date" }),
|
|
2424
2502
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2425
2503
|
/* @__PURE__ */ jsx(
|
|
@@ -2439,1493 +2517,1450 @@ const CouponForm = ({
|
|
|
2439
2517
|
value: endsAt ? dayjs(endsAt).format("HH:mm") : "",
|
|
2440
2518
|
onChange: (e2) => handleDateTimeChange("ends_at", "time", e2.target.value),
|
|
2441
2519
|
disabled,
|
|
2442
|
-
className: "
|
|
2520
|
+
className: "w-32"
|
|
2443
2521
|
}
|
|
2444
2522
|
)
|
|
2445
2523
|
] }),
|
|
2446
2524
|
errors.ends_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
|
|
2447
2525
|
] })
|
|
2448
2526
|
] }),
|
|
2449
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
2450
|
-
/* @__PURE__ */
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2527
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center", children: [
|
|
2528
|
+
/* @__PURE__ */ jsx(Label, { children: "Products" }),
|
|
2529
|
+
/* @__PURE__ */ jsx(
|
|
2530
|
+
Button,
|
|
2531
|
+
{
|
|
2532
|
+
type: "button",
|
|
2533
|
+
variant: "secondary",
|
|
2534
|
+
onClick: () => setOpenProductModal(true),
|
|
2535
|
+
disabled,
|
|
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(
|
|
2458
2553
|
Controller,
|
|
2459
2554
|
{
|
|
2460
|
-
name:
|
|
2555
|
+
name: `products.${index}.discountType`,
|
|
2461
2556
|
control,
|
|
2462
|
-
render: ({ field }) => /* @__PURE__ */ jsxs(
|
|
2557
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsxs(
|
|
2463
2558
|
Select,
|
|
2464
2559
|
{
|
|
2465
|
-
value:
|
|
2466
|
-
onValueChange:
|
|
2560
|
+
value: field2.value,
|
|
2561
|
+
onValueChange: field2.onChange,
|
|
2467
2562
|
disabled,
|
|
2468
2563
|
children: [
|
|
2469
2564
|
/* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select discount type" }) }),
|
|
2470
|
-
/* @__PURE__ */
|
|
2471
|
-
/* @__PURE__ */ jsx(Select.Item, { value: "percentage", children: "Percentage" }),
|
|
2472
|
-
/* @__PURE__ */ jsx(Select.Item, { value: "fixed", children: "Fixed Amount" })
|
|
2473
|
-
] })
|
|
2565
|
+
/* @__PURE__ */ jsx(Select.Content, { children: /* @__PURE__ */ jsx(Select.Item, { value: "percentage", children: "Percentage" }) })
|
|
2474
2566
|
]
|
|
2475
2567
|
}
|
|
2476
2568
|
)
|
|
2477
2569
|
}
|
|
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(
|
|
2570
|
+
) }),
|
|
2571
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
2485
2572
|
Controller,
|
|
2486
2573
|
{
|
|
2487
|
-
name:
|
|
2574
|
+
name: `products.${index}.discountValue`,
|
|
2488
2575
|
control,
|
|
2489
|
-
render: ({ field }) => /* @__PURE__ */ jsx(
|
|
2576
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsx(
|
|
2490
2577
|
Input,
|
|
2491
2578
|
{
|
|
2492
2579
|
type: "number",
|
|
2493
|
-
value:
|
|
2494
|
-
|
|
2495
|
-
step: field.value % 1 === 0 ? 1 : 0.01,
|
|
2496
|
-
onChange: (e2) => field.onChange(Number(e2.target.value)),
|
|
2580
|
+
value: field2.value,
|
|
2581
|
+
onChange: (e2) => field2.onChange(Number(e2.target.value)),
|
|
2497
2582
|
disabled
|
|
2498
2583
|
}
|
|
2499
2584
|
)
|
|
2500
2585
|
}
|
|
2501
|
-
),
|
|
2502
|
-
|
|
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(
|
|
2586
|
+
) }),
|
|
2587
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
2514
2588
|
Controller,
|
|
2515
2589
|
{
|
|
2516
|
-
name:
|
|
2590
|
+
name: `products.${index}.limit`,
|
|
2517
2591
|
control,
|
|
2518
|
-
render: ({ field }) => /* @__PURE__ */
|
|
2519
|
-
|
|
2592
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsx(
|
|
2593
|
+
Input,
|
|
2520
2594
|
{
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
/* @__PURE__ */ jsx(Select.Item, { value: "total", children: "Order Total" }),
|
|
2528
|
-
/* @__PURE__ */ jsx(Select.Item, { value: "each", children: "Each Item" })
|
|
2529
|
-
] })
|
|
2530
|
-
]
|
|
2531
|
-
}
|
|
2532
|
-
)
|
|
2595
|
+
type: "number",
|
|
2596
|
+
value: field2.value,
|
|
2597
|
+
onChange: (e2) => field2.onChange(Number(e2.target.value)),
|
|
2598
|
+
disabled
|
|
2599
|
+
}
|
|
2600
|
+
)
|
|
2533
2601
|
}
|
|
2534
|
-
)
|
|
2535
|
-
|
|
2536
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2537
|
-
/* @__PURE__ */ jsx(Label, { children: "Target Type" }),
|
|
2538
|
-
/* @__PURE__ */ jsx(
|
|
2602
|
+
) }),
|
|
2603
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
2539
2604
|
Controller,
|
|
2540
2605
|
{
|
|
2541
|
-
name:
|
|
2606
|
+
name: `products.${index}.maxQty`,
|
|
2542
2607
|
control,
|
|
2543
|
-
render: ({ field }) => /* @__PURE__ */
|
|
2544
|
-
|
|
2608
|
+
render: ({ field: field2 }) => /* @__PURE__ */ jsx(
|
|
2609
|
+
Input,
|
|
2545
2610
|
{
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
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
|
-
]
|
|
2611
|
+
type: "number",
|
|
2612
|
+
value: field2.value,
|
|
2613
|
+
onChange: (e2) => field2.onChange(Number(e2.target.value)),
|
|
2614
|
+
disabled
|
|
2556
2615
|
}
|
|
2557
2616
|
)
|
|
2558
2617
|
}
|
|
2559
|
-
)
|
|
2560
|
-
|
|
2618
|
+
) }),
|
|
2619
|
+
/* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
|
|
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)) })
|
|
2561
2630
|
] }),
|
|
2562
|
-
/* @__PURE__ */
|
|
2563
|
-
/* @__PURE__ */ jsx(Button, { variant: "secondary", type: "button", onClick: onCancel, children: "Cancel" }),
|
|
2564
|
-
/* @__PURE__ */ jsx(Button, { type: "submit", disabled, children: "Save Coupon" })
|
|
2565
|
-
] })
|
|
2631
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", disabled, children: "Save" })
|
|
2566
2632
|
]
|
|
2567
2633
|
}
|
|
2568
|
-
)
|
|
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
|
+
] }) })
|
|
2569
2654
|
] });
|
|
2570
2655
|
};
|
|
2571
|
-
const
|
|
2572
|
-
const
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
const fetchCoupon = async () => {
|
|
2576
|
-
if (!id) {
|
|
2577
|
-
return;
|
|
2578
|
-
}
|
|
2579
|
-
setIsLoading(true);
|
|
2580
|
-
setError(null);
|
|
2656
|
+
const FlashSaleCreate = () => {
|
|
2657
|
+
const navigate = useNavigate();
|
|
2658
|
+
async function handleSubmit(campaignData) {
|
|
2659
|
+
var _a;
|
|
2581
2660
|
try {
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2661
|
+
await axios.post("/admin/flash-sales", {
|
|
2662
|
+
...campaignData,
|
|
2663
|
+
starts_at: new Date(campaignData.starts_at).toUTCString(),
|
|
2664
|
+
ends_at: new Date(campaignData.ends_at).toUTCString()
|
|
2665
|
+
});
|
|
2666
|
+
toast.success("Flash sale created successfully");
|
|
2667
|
+
navigate("/flash-sales");
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
let message;
|
|
2670
|
+
if (axios.isAxiosError(error)) {
|
|
2671
|
+
console.log(error);
|
|
2672
|
+
message = (_a = error.response) == null ? void 0 : _a.data.message;
|
|
2673
|
+
} else if (error instanceof Error) {
|
|
2674
|
+
message = error.message;
|
|
2675
|
+
} else {
|
|
2676
|
+
message = "Failed to create flash sale";
|
|
2677
|
+
}
|
|
2678
|
+
toast.error("Failed to create flash sale", {
|
|
2679
|
+
description: message
|
|
2680
|
+
});
|
|
2588
2681
|
}
|
|
2589
|
-
}
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
refetch: fetchCoupon
|
|
2598
|
-
};
|
|
2682
|
+
}
|
|
2683
|
+
return /* @__PURE__ */ jsx(
|
|
2684
|
+
FlashSaleForm,
|
|
2685
|
+
{
|
|
2686
|
+
onSubmit: handleSubmit,
|
|
2687
|
+
onCancel: () => navigate("/flash-sales")
|
|
2688
|
+
}
|
|
2689
|
+
);
|
|
2599
2690
|
};
|
|
2600
|
-
const
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2691
|
+
const couponSchema = z.object({
|
|
2692
|
+
name: z.string().min(1, "Name is required"),
|
|
2693
|
+
description: z.string().min(1, "Description is required"),
|
|
2694
|
+
type: z.literal("coupon"),
|
|
2695
|
+
code: z.string().min(1, "Coupon code is required"),
|
|
2696
|
+
discount_type: z.enum(["percentage", "fixed"]),
|
|
2697
|
+
discount_value: z.number().positive("Discount must be positive"),
|
|
2698
|
+
currency_code: z.string().optional(),
|
|
2699
|
+
starts_at: z.string().min(1, "Start date is required"),
|
|
2700
|
+
ends_at: z.string().min(1, "End date is required"),
|
|
2701
|
+
allocation: z.enum(["each", "total"]).optional(),
|
|
2702
|
+
target_type: z.enum(["order", "items"]).optional()
|
|
2703
|
+
}).superRefine((data, ctx) => {
|
|
2704
|
+
if (new Date(data.ends_at) < new Date(data.starts_at)) {
|
|
2705
|
+
ctx.addIssue({
|
|
2706
|
+
code: z.ZodIssueCode.custom,
|
|
2707
|
+
path: ["ends_at"],
|
|
2708
|
+
message: "End date must be after start date"
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
if (data.discount_type === "fixed" && !data.currency_code) {
|
|
2712
|
+
ctx.addIssue({
|
|
2713
|
+
code: z.ZodIssueCode.custom,
|
|
2714
|
+
path: ["currency_code"],
|
|
2715
|
+
message: "Currency is required for fixed discount"
|
|
2716
|
+
});
|
|
2717
|
+
}
|
|
2718
|
+
});
|
|
2719
|
+
const CouponForm = ({
|
|
2720
|
+
initialData,
|
|
2721
|
+
onSubmit,
|
|
2722
|
+
onCancel,
|
|
2723
|
+
disabled = false
|
|
2608
2724
|
}) => {
|
|
2609
|
-
const
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
)
|
|
2626
|
-
|
|
2725
|
+
const {
|
|
2726
|
+
register,
|
|
2727
|
+
control,
|
|
2728
|
+
handleSubmit,
|
|
2729
|
+
watch,
|
|
2730
|
+
setValue,
|
|
2731
|
+
formState: { errors }
|
|
2732
|
+
} = useForm({
|
|
2733
|
+
resolver: t(couponSchema),
|
|
2734
|
+
defaultValues: {
|
|
2735
|
+
name: (initialData == null ? void 0 : initialData.name) ?? "",
|
|
2736
|
+
description: (initialData == null ? void 0 : initialData.description) ?? "",
|
|
2737
|
+
type: "coupon",
|
|
2738
|
+
code: (initialData == null ? void 0 : initialData.code) ?? "",
|
|
2739
|
+
discount_type: (initialData == null ? void 0 : initialData.discount_type) ?? "percentage",
|
|
2740
|
+
discount_value: (initialData == null ? void 0 : initialData.discount_value) ?? 0,
|
|
2741
|
+
currency_code: (initialData == null ? void 0 : initialData.currency_code) ?? "",
|
|
2742
|
+
starts_at: (initialData == null ? void 0 : initialData.starts_at) ?? dayjs().startOf("day").format("YYYY-MM-DDTHH:mm"),
|
|
2743
|
+
ends_at: (initialData == null ? void 0 : initialData.ends_at) ?? dayjs().endOf("day").format("YYYY-MM-DDTHH:mm"),
|
|
2744
|
+
allocation: (initialData == null ? void 0 : initialData.allocation) ?? "total",
|
|
2745
|
+
target_type: (initialData == null ? void 0 : initialData.target_type) ?? "order"
|
|
2746
|
+
}
|
|
2747
|
+
});
|
|
2748
|
+
const discountType = watch("discount_type");
|
|
2749
|
+
const startsAt = watch("starts_at");
|
|
2750
|
+
const endsAt = watch("ends_at");
|
|
2751
|
+
const handleDateTimeChange = (field, type, value) => {
|
|
2752
|
+
const currentValue = watch(field);
|
|
2753
|
+
if (type === "date") {
|
|
2754
|
+
const time = currentValue ? dayjs(currentValue).format("HH:mm") : field === "starts_at" ? "00:00" : "23:59";
|
|
2755
|
+
setValue(field, `${value}T${time}`);
|
|
2756
|
+
} else {
|
|
2757
|
+
const date = currentValue ? dayjs(currentValue).format("YYYY-MM-DD") : dayjs().format("YYYY-MM-DD");
|
|
2758
|
+
setValue(field, `${date}T${value}`);
|
|
2759
|
+
}
|
|
2627
2760
|
};
|
|
2628
|
-
const
|
|
2629
|
-
|
|
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;
|
|
2761
|
+
const onFormSubmit = (data) => {
|
|
2762
|
+
onSubmit(data);
|
|
2662
2763
|
};
|
|
2663
|
-
|
|
2764
|
+
const onFormError = () => {
|
|
2765
|
+
const errorMessages = Object.values(errors).map((err) => err == null ? void 0 : err.message).filter(Boolean).join(", ");
|
|
2766
|
+
toast.error("Invalid coupon data", {
|
|
2767
|
+
description: errorMessages
|
|
2768
|
+
});
|
|
2769
|
+
};
|
|
2770
|
+
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
2664
2771
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2665
|
-
/* @__PURE__ */ jsx(
|
|
2666
|
-
|
|
2667
|
-
/* @__PURE__ */ jsx(InformationCircle, { className: "h-4 w-4" }),
|
|
2668
|
-
/* @__PURE__ */ jsx("span", { children: helpText })
|
|
2669
|
-
] })
|
|
2670
|
-
] }),
|
|
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
|
-
)
|
|
2772
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "Coupon" }),
|
|
2773
|
+
/* @__PURE__ */ jsx(Button, { variant: "transparent", onClick: onCancel, children: "Cancel" })
|
|
2860
2774
|
] }),
|
|
2861
|
-
/* @__PURE__ */
|
|
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
|
-
|
|
2775
|
+
/* @__PURE__ */ jsxs(
|
|
2776
|
+
"form",
|
|
2777
|
+
{
|
|
2778
|
+
onSubmit: handleSubmit(onFormSubmit, onFormError),
|
|
2779
|
+
className: "space-y-6 my-8",
|
|
2780
|
+
children: [
|
|
2781
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2782
|
+
/* @__PURE__ */ jsx(Label, { children: "Name" }),
|
|
2783
|
+
/* @__PURE__ */ jsx(Input, { ...register("name"), disabled }),
|
|
2784
|
+
errors.name && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.name.message })
|
|
2785
|
+
] }),
|
|
2786
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2787
|
+
/* @__PURE__ */ jsx(Label, { children: "Description" }),
|
|
2788
|
+
/* @__PURE__ */ jsx(Textarea, { ...register("description"), disabled }),
|
|
2789
|
+
errors.description && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
|
|
2790
|
+
] }),
|
|
2791
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2792
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2793
|
+
/* @__PURE__ */ jsx(Label, { children: "Start Date" }),
|
|
2794
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2795
|
+
/* @__PURE__ */ jsx(
|
|
2796
|
+
Input,
|
|
2797
|
+
{
|
|
2798
|
+
type: "date",
|
|
2799
|
+
value: startsAt ? dayjs(startsAt).format("YYYY-MM-DD") : "",
|
|
2800
|
+
onChange: (e2) => handleDateTimeChange("starts_at", "date", e2.target.value),
|
|
2801
|
+
disabled,
|
|
2802
|
+
className: "flex-1"
|
|
2803
|
+
}
|
|
2804
|
+
),
|
|
2805
|
+
/* @__PURE__ */ jsx(
|
|
2806
|
+
Input,
|
|
2807
|
+
{
|
|
2808
|
+
type: "time",
|
|
2809
|
+
value: startsAt ? dayjs(startsAt).format("HH:mm") : "",
|
|
2810
|
+
onChange: (e2) => handleDateTimeChange("starts_at", "time", e2.target.value),
|
|
2811
|
+
disabled,
|
|
2812
|
+
className: "flex-1"
|
|
2813
|
+
}
|
|
2814
|
+
)
|
|
2815
|
+
] }),
|
|
2816
|
+
errors.starts_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
|
|
2817
|
+
] }),
|
|
2818
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2819
|
+
/* @__PURE__ */ jsx(Label, { children: "End Date" }),
|
|
2820
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2821
|
+
/* @__PURE__ */ jsx(
|
|
2822
|
+
Input,
|
|
2823
|
+
{
|
|
2824
|
+
type: "date",
|
|
2825
|
+
value: endsAt ? dayjs(endsAt).format("YYYY-MM-DD") : "",
|
|
2826
|
+
onChange: (e2) => handleDateTimeChange("ends_at", "date", e2.target.value),
|
|
2827
|
+
disabled,
|
|
2828
|
+
className: "flex-1"
|
|
2829
|
+
}
|
|
2830
|
+
),
|
|
2831
|
+
/* @__PURE__ */ jsx(
|
|
2832
|
+
Input,
|
|
2833
|
+
{
|
|
2834
|
+
type: "time",
|
|
2835
|
+
value: endsAt ? dayjs(endsAt).format("HH:mm") : "",
|
|
2836
|
+
onChange: (e2) => handleDateTimeChange("ends_at", "time", e2.target.value),
|
|
2837
|
+
disabled,
|
|
2838
|
+
className: "flex-1"
|
|
2839
|
+
}
|
|
2840
|
+
)
|
|
2841
|
+
] }),
|
|
2842
|
+
errors.ends_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
|
|
2843
|
+
] })
|
|
2844
|
+
] }),
|
|
2845
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2846
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2847
|
+
/* @__PURE__ */ jsx(Label, { children: "Coupon Code" }),
|
|
2848
|
+
/* @__PURE__ */ jsx(Input, { ...register("code"), disabled }),
|
|
2849
|
+
errors.code && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.code.message })
|
|
2850
|
+
] }),
|
|
2851
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2852
|
+
/* @__PURE__ */ jsx(Label, { children: "Discount Type" }),
|
|
2853
|
+
/* @__PURE__ */ jsx(
|
|
2854
|
+
Controller,
|
|
2855
|
+
{
|
|
2856
|
+
name: "discount_type",
|
|
2857
|
+
control,
|
|
2858
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(
|
|
2859
|
+
Select,
|
|
2860
|
+
{
|
|
2861
|
+
value: field.value,
|
|
2862
|
+
onValueChange: field.onChange,
|
|
2863
|
+
disabled,
|
|
2864
|
+
children: [
|
|
2865
|
+
/* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select discount type" }) }),
|
|
2866
|
+
/* @__PURE__ */ jsxs(Select.Content, { children: [
|
|
2867
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "percentage", children: "Percentage" }),
|
|
2868
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "fixed", children: "Fixed Amount" })
|
|
2869
|
+
] })
|
|
2870
|
+
]
|
|
2871
|
+
}
|
|
2872
|
+
)
|
|
2873
|
+
}
|
|
2874
|
+
)
|
|
2875
|
+
] })
|
|
2876
|
+
] }),
|
|
2877
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2878
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2879
|
+
/* @__PURE__ */ jsx(Label, { children: "Discount Value" }),
|
|
2880
|
+
/* @__PURE__ */ jsx(
|
|
2881
|
+
Controller,
|
|
2882
|
+
{
|
|
2883
|
+
name: "discount_value",
|
|
2884
|
+
control,
|
|
2885
|
+
render: ({ field }) => /* @__PURE__ */ jsx(
|
|
2886
|
+
Input,
|
|
2887
|
+
{
|
|
2888
|
+
type: "number",
|
|
2889
|
+
value: field.value,
|
|
2890
|
+
min: 0,
|
|
2891
|
+
step: field.value % 1 === 0 ? 1 : 0.01,
|
|
2892
|
+
onChange: (e2) => field.onChange(Number(e2.target.value)),
|
|
2893
|
+
disabled
|
|
2894
|
+
}
|
|
2895
|
+
)
|
|
2896
|
+
}
|
|
2897
|
+
),
|
|
2898
|
+
errors.discount_value && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.discount_value.message })
|
|
2899
|
+
] }),
|
|
2900
|
+
discountType === "fixed" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2901
|
+
/* @__PURE__ */ jsx(Label, { children: "Currency Code" }),
|
|
2902
|
+
/* @__PURE__ */ jsx(Input, { ...register("currency_code"), disabled }),
|
|
2903
|
+
errors.currency_code && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.currency_code.message })
|
|
2904
|
+
] })
|
|
2905
|
+
] }),
|
|
2906
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
2907
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2908
|
+
/* @__PURE__ */ jsx(Label, { children: "Allocation" }),
|
|
2909
|
+
/* @__PURE__ */ jsx(
|
|
2910
|
+
Controller,
|
|
2911
|
+
{
|
|
2912
|
+
name: "allocation",
|
|
2913
|
+
control,
|
|
2914
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(
|
|
2915
|
+
Select,
|
|
2916
|
+
{
|
|
2917
|
+
value: field.value,
|
|
2918
|
+
onValueChange: field.onChange,
|
|
2919
|
+
disabled,
|
|
2920
|
+
children: [
|
|
2921
|
+
/* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select allocation" }) }),
|
|
2922
|
+
/* @__PURE__ */ jsxs(Select.Content, { children: [
|
|
2923
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "total", children: "Order Total" }),
|
|
2924
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "each", children: "Each Item" })
|
|
2925
|
+
] })
|
|
2926
|
+
]
|
|
2927
|
+
}
|
|
2928
|
+
)
|
|
2929
|
+
}
|
|
2930
|
+
)
|
|
2931
|
+
] }),
|
|
2932
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2933
|
+
/* @__PURE__ */ jsx(Label, { children: "Target Type" }),
|
|
2934
|
+
/* @__PURE__ */ jsx(
|
|
2935
|
+
Controller,
|
|
2936
|
+
{
|
|
2937
|
+
name: "target_type",
|
|
2938
|
+
control,
|
|
2939
|
+
render: ({ field }) => /* @__PURE__ */ jsxs(
|
|
2940
|
+
Select,
|
|
2941
|
+
{
|
|
2942
|
+
value: field.value,
|
|
2943
|
+
onValueChange: field.onChange,
|
|
2944
|
+
disabled,
|
|
2945
|
+
children: [
|
|
2946
|
+
/* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select target" }) }),
|
|
2947
|
+
/* @__PURE__ */ jsxs(Select.Content, { children: [
|
|
2948
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "order", children: "Order" }),
|
|
2949
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "items", children: "Items" })
|
|
2950
|
+
] })
|
|
2951
|
+
]
|
|
2952
|
+
}
|
|
2953
|
+
)
|
|
2954
|
+
}
|
|
2955
|
+
)
|
|
2956
|
+
] })
|
|
2957
|
+
] }),
|
|
2958
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
2959
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", type: "button", onClick: onCancel, children: "Cancel" }),
|
|
2960
|
+
/* @__PURE__ */ jsx(Button, { type: "submit", disabled, children: "Save Coupon" })
|
|
2961
|
+
] })
|
|
2962
|
+
]
|
|
2927
2963
|
}
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2964
|
+
)
|
|
2965
|
+
] });
|
|
2966
|
+
};
|
|
2967
|
+
const useCouponById = (id) => {
|
|
2968
|
+
const [data, setData] = useState(null);
|
|
2969
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
2970
|
+
const [error, setError] = useState(null);
|
|
2971
|
+
const fetchCoupon = async () => {
|
|
2972
|
+
if (!id) {
|
|
2973
|
+
return;
|
|
2934
2974
|
}
|
|
2935
|
-
|
|
2936
|
-
const handleDelete = async () => {
|
|
2937
|
-
if (!confirm(`Are you sure you want to delete the ${imageType}?`)) return;
|
|
2938
|
-
setIsUploading(true);
|
|
2975
|
+
setIsLoading(true);
|
|
2939
2976
|
setError(null);
|
|
2940
2977
|
try {
|
|
2941
|
-
const response = await
|
|
2942
|
-
|
|
2943
|
-
{
|
|
2944
|
-
method: "DELETE",
|
|
2945
|
-
credentials: "include"
|
|
2946
|
-
}
|
|
2947
|
-
);
|
|
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
|
-
}
|
|
2978
|
+
const response = await axios.get(`/admin/coupons/${id}`);
|
|
2979
|
+
setData(response.data);
|
|
2959
2980
|
} catch (err) {
|
|
2960
|
-
setError(
|
|
2961
|
-
console.error(`Error deleting ${imageType}:`, err);
|
|
2981
|
+
setError(err instanceof Error ? err : new Error("Failed to fetch coupon"));
|
|
2962
2982
|
} finally {
|
|
2963
|
-
|
|
2983
|
+
setIsLoading(false);
|
|
2964
2984
|
}
|
|
2965
2985
|
};
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
2986
|
+
useEffect(() => {
|
|
2987
|
+
fetchCoupon();
|
|
2988
|
+
}, [id]);
|
|
2989
|
+
return {
|
|
2990
|
+
data,
|
|
2991
|
+
isLoading,
|
|
2992
|
+
error,
|
|
2993
|
+
refetch: fetchCoupon
|
|
2994
|
+
};
|
|
2995
|
+
};
|
|
2996
|
+
const MarkdownEditor = ({
|
|
2997
|
+
label,
|
|
2998
|
+
value,
|
|
2999
|
+
onChange,
|
|
3000
|
+
placeholder,
|
|
3001
|
+
helpText,
|
|
3002
|
+
rows = 10,
|
|
3003
|
+
showPreview = true
|
|
3004
|
+
}) => {
|
|
3005
|
+
const [activeTab, setActiveTab] = useState("write");
|
|
3006
|
+
const insertMarkdown = (before, after = "") => {
|
|
3007
|
+
const textarea = document.getElementById(
|
|
3008
|
+
`markdown-${label}`
|
|
3009
|
+
);
|
|
3010
|
+
if (!textarea) return;
|
|
3011
|
+
const start = textarea.selectionStart;
|
|
3012
|
+
const end = textarea.selectionEnd;
|
|
3013
|
+
const selectedText = value.substring(start, end);
|
|
3014
|
+
const newText = value.substring(0, start) + before + selectedText + after + value.substring(end);
|
|
3015
|
+
onChange(newText);
|
|
3016
|
+
setTimeout(() => {
|
|
3017
|
+
textarea.focus();
|
|
3018
|
+
textarea.setSelectionRange(
|
|
3019
|
+
start + before.length,
|
|
3020
|
+
start + before.length + selectedText.length
|
|
3021
|
+
);
|
|
3022
|
+
}, 0);
|
|
3023
|
+
};
|
|
3024
|
+
const renderMarkdownPreview = (markdown) => {
|
|
3025
|
+
let html = markdown;
|
|
3026
|
+
html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
|
|
3027
|
+
html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
|
|
3028
|
+
html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
|
|
3029
|
+
html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
|
|
3030
|
+
html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
|
|
3031
|
+
html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
|
|
3032
|
+
html = html.replace(/_(.+?)_/g, "<em>$1</em>");
|
|
3033
|
+
html = html.replace(
|
|
3034
|
+
/```(\w+)?\n([\s\S]+?)```/g,
|
|
3035
|
+
'<pre class="bg-ui-bg-subtle p-4 rounded-lg overflow-x-auto"><code class="language-$1">$2</code></pre>'
|
|
3036
|
+
);
|
|
3037
|
+
html = html.replace(
|
|
3038
|
+
/`([^`]+)`/g,
|
|
3039
|
+
'<code class="bg-ui-bg-subtle px-1 py-0.5 rounded text-sm">$1</code>'
|
|
3040
|
+
);
|
|
3041
|
+
html = html.replace(
|
|
3042
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
3043
|
+
'<a href="$2" class="text-ui-fg-interactive underline">$1</a>'
|
|
3044
|
+
);
|
|
3045
|
+
html = html.replace(
|
|
3046
|
+
/!\[([^\]]*)\]\(([^)]+)\)/g,
|
|
3047
|
+
'<img src="$2" alt="$1" class="max-w-full h-auto rounded-lg my-2" />'
|
|
3048
|
+
);
|
|
3049
|
+
html = html.replace(/^\* (.+)$/gim, "<li>$1</li>");
|
|
3050
|
+
html = html.replace(/^\- (.+)$/gim, "<li>$1</li>");
|
|
3051
|
+
html = html.replace(
|
|
3052
|
+
/(<li>.*<\/li>)/s,
|
|
3053
|
+
"<ul class='list-disc ml-6'>$1</ul>"
|
|
3054
|
+
);
|
|
3055
|
+
html = html.replace(/\n\n/g, "</p><p>");
|
|
3056
|
+
html = `<p>${html}</p>`;
|
|
3057
|
+
return html;
|
|
3058
|
+
};
|
|
3059
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3060
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3061
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: `markdown-${label}`, className: "font-medium", children: label }),
|
|
3062
|
+
helpText && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-ui-fg-subtle", children: [
|
|
3063
|
+
/* @__PURE__ */ jsx(InformationCircle, { className: "h-4 w-4" }),
|
|
3064
|
+
/* @__PURE__ */ jsx("span", { children: helpText })
|
|
3065
|
+
] })
|
|
3001
3066
|
] }),
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
/* @__PURE__ */
|
|
3067
|
+
showPreview ? /* @__PURE__ */ jsxs(Tabs, { value: activeTab, onValueChange: (v) => setActiveTab(v), children: [
|
|
3068
|
+
/* @__PURE__ */ jsxs(Tabs.List, { children: [
|
|
3069
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "write", children: "Write" }),
|
|
3070
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "preview", children: "Preview" })
|
|
3071
|
+
] }),
|
|
3072
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "write", className: "mt-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3073
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
|
|
3006
3074
|
/* @__PURE__ */ jsx(
|
|
3007
|
-
|
|
3075
|
+
Button,
|
|
3008
3076
|
{
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
)
|
|
3077
|
+
type: "button",
|
|
3078
|
+
variant: "secondary",
|
|
3079
|
+
size: "small",
|
|
3080
|
+
onClick: () => insertMarkdown("**", "**"),
|
|
3081
|
+
title: "Bold",
|
|
3082
|
+
children: /* @__PURE__ */ jsx("strong", { children: "B" })
|
|
3015
3083
|
}
|
|
3016
3084
|
),
|
|
3017
|
-
|
|
3018
|
-
] }),
|
|
3019
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3020
|
-
/* @__PURE__ */ jsxs(
|
|
3085
|
+
/* @__PURE__ */ jsx(
|
|
3021
3086
|
Button,
|
|
3022
3087
|
{
|
|
3088
|
+
type: "button",
|
|
3023
3089
|
variant: "secondary",
|
|
3024
|
-
|
|
3025
|
-
onClick: () =>
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
},
|
|
3029
|
-
children: [
|
|
3030
|
-
/* @__PURE__ */ jsx(CloudArrowUp, { className: "mr-2" }),
|
|
3031
|
-
"Replace"
|
|
3032
|
-
]
|
|
3090
|
+
size: "small",
|
|
3091
|
+
onClick: () => insertMarkdown("*", "*"),
|
|
3092
|
+
title: "Italic",
|
|
3093
|
+
children: /* @__PURE__ */ jsx("em", { children: "I" })
|
|
3033
3094
|
}
|
|
3034
3095
|
),
|
|
3035
|
-
/* @__PURE__ */
|
|
3096
|
+
/* @__PURE__ */ jsx(
|
|
3036
3097
|
Button,
|
|
3037
3098
|
{
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
]
|
|
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" })
|
|
3045
3105
|
}
|
|
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
3106
|
),
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
"
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
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 })
|
|
3197
|
-
] }),
|
|
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,
|
|
3311
|
-
{
|
|
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
|
|
3319
|
-
}
|
|
3320
|
-
) }),
|
|
3321
|
-
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsx(
|
|
3322
|
-
MarkdownEditor,
|
|
3323
|
-
{
|
|
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
|
|
3331
|
-
}
|
|
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
|
-
] })
|
|
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
|
+
),
|
|
3140
|
+
/* @__PURE__ */ jsx(
|
|
3141
|
+
Button,
|
|
3142
|
+
{
|
|
3143
|
+
type: "button",
|
|
3144
|
+
variant: "secondary",
|
|
3145
|
+
size: "small",
|
|
3146
|
+
onClick: () => insertMarkdown(""),
|
|
3147
|
+
title: "Image",
|
|
3148
|
+
children: "Image"
|
|
3149
|
+
}
|
|
3150
|
+
),
|
|
3151
|
+
/* @__PURE__ */ jsx(
|
|
3152
|
+
Button,
|
|
3153
|
+
{
|
|
3154
|
+
type: "button",
|
|
3155
|
+
variant: "secondary",
|
|
3156
|
+
size: "small",
|
|
3157
|
+
onClick: () => insertMarkdown("```\n", "\n```"),
|
|
3158
|
+
title: "Code Block",
|
|
3159
|
+
children: "Code"
|
|
3160
|
+
}
|
|
3161
|
+
),
|
|
3162
|
+
/* @__PURE__ */ jsx(
|
|
3163
|
+
Button,
|
|
3164
|
+
{
|
|
3165
|
+
type: "button",
|
|
3166
|
+
variant: "secondary",
|
|
3167
|
+
size: "small",
|
|
3168
|
+
onClick: () => insertMarkdown("- "),
|
|
3169
|
+
title: "List",
|
|
3170
|
+
children: "List"
|
|
3171
|
+
}
|
|
3172
|
+
)
|
|
3173
|
+
] }),
|
|
3174
|
+
/* @__PURE__ */ jsx(
|
|
3175
|
+
Textarea,
|
|
3176
|
+
{
|
|
3177
|
+
id: `markdown-${label}`,
|
|
3178
|
+
value,
|
|
3179
|
+
onChange: (e2) => onChange(e2.target.value),
|
|
3180
|
+
placeholder,
|
|
3181
|
+
rows,
|
|
3182
|
+
className: "font-mono text-sm"
|
|
3183
|
+
}
|
|
3184
|
+
)
|
|
3185
|
+
] }) }),
|
|
3186
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "preview", className: "mt-4", children: /* @__PURE__ */ jsx(
|
|
3187
|
+
"div",
|
|
3188
|
+
{
|
|
3189
|
+
className: clx(
|
|
3190
|
+
"min-h-[200px] rounded-lg border border-ui-border-base bg-ui-bg-subtle p-4",
|
|
3191
|
+
"prose prose-sm max-w-none"
|
|
3192
|
+
),
|
|
3193
|
+
dangerouslySetInnerHTML: {
|
|
3194
|
+
__html: value ? renderMarkdownPreview(value) : '<p class="text-ui-fg-subtle">Nothing to preview</p>'
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
) })
|
|
3198
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3199
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
|
|
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
|
+
)
|
|
3427
3244
|
] }),
|
|
3428
|
-
/* @__PURE__ */ jsx(
|
|
3245
|
+
/* @__PURE__ */ jsx(
|
|
3246
|
+
Textarea,
|
|
3247
|
+
{
|
|
3248
|
+
id: `markdown-${label}`,
|
|
3249
|
+
value,
|
|
3250
|
+
onChange: (e2) => onChange(e2.target.value),
|
|
3251
|
+
placeholder,
|
|
3252
|
+
rows,
|
|
3253
|
+
className: "font-mono text-sm"
|
|
3254
|
+
}
|
|
3255
|
+
)
|
|
3429
3256
|
] }),
|
|
3430
|
-
|
|
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
|
-
)
|
|
3257
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-ui-fg-subtle", children: "Supports Markdown formatting: **bold**, *italic*, `code`, ```code blocks```, [links](url), , and lists" })
|
|
3440
3258
|
] });
|
|
3441
3259
|
};
|
|
3442
|
-
const
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
const
|
|
3455
|
-
const
|
|
3456
|
-
const
|
|
3457
|
-
|
|
3458
|
-
|
|
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"
|
|
3260
|
+
const CampaignImageUploader = ({
|
|
3261
|
+
campaignId,
|
|
3262
|
+
imageType,
|
|
3263
|
+
currentImageUrl,
|
|
3264
|
+
onClose,
|
|
3265
|
+
onSuccess
|
|
3266
|
+
}) => {
|
|
3267
|
+
const [displayImage, setDisplayImage] = useState(currentImageUrl || null);
|
|
3268
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
3269
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
3270
|
+
const [error, setError] = useState(null);
|
|
3271
|
+
const [previewUrl, setPreviewUrl] = useState(null);
|
|
3272
|
+
const isThumbnail = imageType === "thumbnail";
|
|
3273
|
+
const maxSize = 5;
|
|
3274
|
+
const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
3275
|
+
const formatFileTypes = () => {
|
|
3276
|
+
return "JPEG, PNG, GIF, or WebP";
|
|
3468
3277
|
};
|
|
3469
|
-
const
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
return;
|
|
3278
|
+
const validateFile = (file) => {
|
|
3279
|
+
if (!allowedTypes.includes(file.type)) {
|
|
3280
|
+
setError(`Please upload a valid image file (${formatFileTypes()})`);
|
|
3281
|
+
return false;
|
|
3282
|
+
}
|
|
3283
|
+
if (file.size > maxSize * 1024 * 1024) {
|
|
3284
|
+
setError(`File size must be less than ${maxSize}MB`);
|
|
3285
|
+
return false;
|
|
3473
3286
|
}
|
|
3287
|
+
return true;
|
|
3288
|
+
};
|
|
3289
|
+
const handleFileUpload = async (file) => {
|
|
3290
|
+
var _a, _b;
|
|
3291
|
+
if (!validateFile(file)) return;
|
|
3292
|
+
setError(null);
|
|
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);
|
|
3474
3301
|
try {
|
|
3475
|
-
await
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
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);
|
|
3490
3323
|
}
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3324
|
+
} catch (err) {
|
|
3325
|
+
setError(`Error uploading ${imageType}`);
|
|
3326
|
+
setPreviewUrl(null);
|
|
3327
|
+
console.error(`Error uploading ${imageType}:`, err);
|
|
3328
|
+
} finally {
|
|
3329
|
+
setIsUploading(false);
|
|
3330
|
+
}
|
|
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
|
+
try {
|
|
3337
|
+
const response = await fetch(
|
|
3338
|
+
`/admin/campaigns/${campaignId}/${imageType}`,
|
|
3339
|
+
{
|
|
3340
|
+
method: "DELETE",
|
|
3341
|
+
credentials: "include"
|
|
3342
|
+
}
|
|
3343
|
+
);
|
|
3344
|
+
if (response.ok) {
|
|
3345
|
+
setDisplayImage(null);
|
|
3346
|
+
setPreviewUrl(null);
|
|
3347
|
+
onSuccess == null ? void 0 : onSuccess();
|
|
3348
|
+
setTimeout(() => {
|
|
3349
|
+
onClose();
|
|
3350
|
+
}, 500);
|
|
3351
|
+
} else {
|
|
3352
|
+
const errorData = await response.json();
|
|
3353
|
+
setError(errorData.error || `Failed to delete ${imageType}`);
|
|
3354
|
+
}
|
|
3355
|
+
} catch (err) {
|
|
3356
|
+
setError(`Error deleting ${imageType}`);
|
|
3357
|
+
console.error(`Error deleting ${imageType}:`, err);
|
|
3358
|
+
} finally {
|
|
3359
|
+
setIsUploading(false);
|
|
3494
3360
|
}
|
|
3495
3361
|
};
|
|
3496
|
-
|
|
3362
|
+
const handleDragEnter = useCallback((e2) => {
|
|
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: [
|
|
3497
3388
|
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
3498
3389
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
3499
|
-
/* @__PURE__ */
|
|
3500
|
-
|
|
3390
|
+
/* @__PURE__ */ jsxs(Heading, { level: "h2", children: [
|
|
3391
|
+
"Upload Campaign ",
|
|
3392
|
+
isThumbnail ? "Thumbnail" : "Image"
|
|
3393
|
+
] }),
|
|
3394
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: isThumbnail ? "Thumbnail for campaign listing" : "Main campaign image" })
|
|
3501
3395
|
] }),
|
|
3502
|
-
/* @__PURE__ */
|
|
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
|
-
] })
|
|
3396
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsx(X, {}) })
|
|
3506
3397
|
] }),
|
|
3507
|
-
/* @__PURE__ */
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
/* @__PURE__ */
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3398
|
+
error && /* @__PURE__ */ jsx(Alert, { variant: "error", dismissible: true, className: "mb-4", children: error }),
|
|
3399
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3400
|
+
imageToDisplay ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3401
|
+
/* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-subtle", children: [
|
|
3402
|
+
/* @__PURE__ */ jsx(
|
|
3403
|
+
"img",
|
|
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",
|
|
3514
3446
|
{
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3447
|
+
className: clx(
|
|
3448
|
+
"flex flex-col items-center justify-center rounded-lg border-2 border-dashed transition-colors",
|
|
3449
|
+
isThumbnail ? "h-48" : "h-64",
|
|
3450
|
+
isDragging ? "border-ui-border-interactive bg-ui-bg-highlight" : "border-ui-border-base bg-ui-bg-subtle",
|
|
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
|
+
]
|
|
3519
3475
|
}
|
|
3520
|
-
)
|
|
3521
|
-
/* @__PURE__ */ jsx(
|
|
3522
|
-
|
|
3476
|
+
),
|
|
3477
|
+
/* @__PURE__ */ jsx(
|
|
3478
|
+
"input",
|
|
3523
3479
|
{
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
}
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
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
|
-
] });
|
|
3480
|
+
id: `${imageType}-file-input`,
|
|
3481
|
+
type: "file",
|
|
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"
|
|
3490
|
+
}
|
|
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" })
|
|
3496
|
+
] })
|
|
3497
|
+
] }) });
|
|
3638
3498
|
};
|
|
3639
|
-
const
|
|
3640
|
-
|
|
3641
|
-
|
|
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
|
|
3499
|
+
const CampaignDetailForm = ({
|
|
3500
|
+
campaignId,
|
|
3501
|
+
campaignName
|
|
3671
3502
|
}) => {
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
ends_at: (initialData == null ? void 0 : initialData.ends_at) || "",
|
|
3688
|
-
products: initialProducts ? Array.from(initialProducts.values()) : []
|
|
3689
|
-
}
|
|
3690
|
-
});
|
|
3691
|
-
const { fields, append, remove } = useFieldArray({
|
|
3692
|
-
control,
|
|
3693
|
-
name: "products"
|
|
3503
|
+
const [campaignDetail, setCampaignDetail] = useState(
|
|
3504
|
+
null
|
|
3505
|
+
);
|
|
3506
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
3507
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
3508
|
+
const [showImageUploader, setShowImageUploader] = useState(null);
|
|
3509
|
+
const [formData, setFormData] = useState({
|
|
3510
|
+
detail_content: "",
|
|
3511
|
+
terms_and_conditions: "",
|
|
3512
|
+
meta_title: "",
|
|
3513
|
+
meta_description: "",
|
|
3514
|
+
meta_keywords: "",
|
|
3515
|
+
link_url: "",
|
|
3516
|
+
link_text: "",
|
|
3517
|
+
display_order: 0
|
|
3694
3518
|
});
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
const
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
const
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3519
|
+
useEffect(() => {
|
|
3520
|
+
fetchCampaignDetail();
|
|
3521
|
+
}, [campaignId]);
|
|
3522
|
+
const fetchCampaignDetail = async () => {
|
|
3523
|
+
setIsLoading(true);
|
|
3524
|
+
try {
|
|
3525
|
+
const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
|
|
3526
|
+
credentials: "include"
|
|
3527
|
+
});
|
|
3528
|
+
if (response.ok) {
|
|
3529
|
+
const data = await response.json();
|
|
3530
|
+
if (data.campaign_detail) {
|
|
3531
|
+
console.log("Campaign detail loaded:", {
|
|
3532
|
+
image_url: data.campaign_detail.image_url,
|
|
3533
|
+
thumbnail_url: data.campaign_detail.thumbnail_url,
|
|
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);
|
|
3706
3556
|
}
|
|
3707
3557
|
};
|
|
3708
|
-
const
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3558
|
+
const handleSave = async () => {
|
|
3559
|
+
setIsSaving(true);
|
|
3560
|
+
try {
|
|
3561
|
+
const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
|
|
3562
|
+
method: "POST",
|
|
3563
|
+
headers: {
|
|
3564
|
+
"Content-Type": "application/json"
|
|
3565
|
+
},
|
|
3566
|
+
body: JSON.stringify(formData),
|
|
3567
|
+
credentials: "include"
|
|
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");
|
|
3718
3576
|
}
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3577
|
+
} catch (error) {
|
|
3578
|
+
console.error("Error saving campaign detail:", error);
|
|
3579
|
+
toast.error("Error saving campaign details");
|
|
3580
|
+
} finally {
|
|
3581
|
+
setIsSaving(false);
|
|
3582
|
+
}
|
|
3724
3583
|
};
|
|
3725
|
-
|
|
3726
|
-
/* @__PURE__ */
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
children:
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
/* @__PURE__ */ jsx(
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
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
|
-
}
|
|
3584
|
+
if (isLoading) {
|
|
3585
|
+
return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { className: "py-8 text-center", children: "Loading campaign details..." }) });
|
|
3586
|
+
}
|
|
3587
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3588
|
+
/* @__PURE__ */ jsxs(Container, { className: "space-y-6", children: [
|
|
3589
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3590
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3591
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Campaign Details" }),
|
|
3592
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-ui-fg-subtle", children: campaignName })
|
|
3593
|
+
] }),
|
|
3594
|
+
/* @__PURE__ */ jsx(Button, { onClick: handleSave, isLoading: isSaving, children: "Save Details" })
|
|
3595
|
+
] }),
|
|
3596
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3597
|
+
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "Images" }),
|
|
3598
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
3599
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3600
|
+
/* @__PURE__ */ jsx(Label, { children: "Main Campaign Image" }),
|
|
3601
|
+
/* @__PURE__ */ jsx(
|
|
3602
|
+
"div",
|
|
3603
|
+
{
|
|
3604
|
+
className: clx(
|
|
3605
|
+
"relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
|
|
3606
|
+
(campaignDetail == null ? void 0 : campaignDetail.image_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
|
|
3759
3607
|
),
|
|
3760
|
-
/* @__PURE__ */
|
|
3761
|
-
|
|
3608
|
+
children: (campaignDetail == null ? void 0 : campaignDetail.image_url) ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3609
|
+
/* @__PURE__ */ jsx(
|
|
3610
|
+
"img",
|
|
3611
|
+
{
|
|
3612
|
+
src: campaignDetail.image_url,
|
|
3613
|
+
alt: "Campaign",
|
|
3614
|
+
className: "h-full w-full object-cover",
|
|
3615
|
+
onError: (e2) => {
|
|
3616
|
+
console.error(
|
|
3617
|
+
"Failed to load image:",
|
|
3618
|
+
campaignDetail.image_url
|
|
3619
|
+
);
|
|
3620
|
+
e2.currentTarget.style.display = "none";
|
|
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,
|
|
3762
3639
|
{
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3640
|
+
variant: "secondary",
|
|
3641
|
+
onClick: () => setShowImageUploader("image"),
|
|
3642
|
+
children: [
|
|
3643
|
+
/* @__PURE__ */ jsx(PhotoSolid, { className: "mr-2" }),
|
|
3644
|
+
"Upload Image"
|
|
3645
|
+
]
|
|
3768
3646
|
}
|
|
3769
3647
|
)
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
onChange: (e2) => handleDateTimeChange("ends_at", "date", e2.target.value),
|
|
3782
|
-
disabled,
|
|
3783
|
-
className: "flex-1"
|
|
3784
|
-
}
|
|
3648
|
+
}
|
|
3649
|
+
)
|
|
3650
|
+
] }),
|
|
3651
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3652
|
+
/* @__PURE__ */ jsx(Label, { children: "Thumbnail" }),
|
|
3653
|
+
/* @__PURE__ */ jsx(
|
|
3654
|
+
"div",
|
|
3655
|
+
{
|
|
3656
|
+
className: clx(
|
|
3657
|
+
"relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
|
|
3658
|
+
(campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
|
|
3785
3659
|
),
|
|
3786
|
-
/* @__PURE__ */
|
|
3787
|
-
|
|
3660
|
+
children: (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3661
|
+
/* @__PURE__ */ jsx(
|
|
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,
|
|
3788
3691
|
{
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3692
|
+
variant: "secondary",
|
|
3693
|
+
onClick: () => setShowImageUploader("thumbnail"),
|
|
3694
|
+
children: [
|
|
3695
|
+
/* @__PURE__ */ jsx(PhotoSolid, { className: "mr-2" }),
|
|
3696
|
+
"Upload Thumbnail"
|
|
3697
|
+
]
|
|
3794
3698
|
}
|
|
3795
3699
|
)
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
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
|
+
)
|
|
3799
3744
|
] }),
|
|
3800
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
3801
|
-
/* @__PURE__ */ jsx(Label, { children: "
|
|
3745
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3746
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "link-text", children: "Link Text" }),
|
|
3802
3747
|
/* @__PURE__ */ jsx(
|
|
3803
|
-
|
|
3748
|
+
Input,
|
|
3804
3749
|
{
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
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"
|
|
3810
3771
|
}
|
|
3811
3772
|
)
|
|
3812
3773
|
] }),
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
/* @__PURE__ */ jsx(
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
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)) })
|
|
3774
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3775
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "meta-description", children: "Meta Description" }),
|
|
3776
|
+
/* @__PURE__ */ jsx(
|
|
3777
|
+
Input,
|
|
3778
|
+
{
|
|
3779
|
+
id: "meta-description",
|
|
3780
|
+
value: formData.meta_description,
|
|
3781
|
+
onChange: (e2) => setFormData({
|
|
3782
|
+
...formData,
|
|
3783
|
+
meta_description: e2.target.value
|
|
3784
|
+
}),
|
|
3785
|
+
placeholder: "Campaign meta description for SEO"
|
|
3786
|
+
}
|
|
3787
|
+
)
|
|
3903
3788
|
] }),
|
|
3904
|
-
/* @__PURE__ */
|
|
3905
|
-
|
|
3789
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3790
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "meta-keywords", children: "Meta Keywords" }),
|
|
3791
|
+
/* @__PURE__ */ jsx(
|
|
3792
|
+
Input,
|
|
3793
|
+
{
|
|
3794
|
+
id: "meta-keywords",
|
|
3795
|
+
value: formData.meta_keywords,
|
|
3796
|
+
onChange: (e2) => setFormData({
|
|
3797
|
+
...formData,
|
|
3798
|
+
meta_keywords: e2.target.value
|
|
3799
|
+
}),
|
|
3800
|
+
placeholder: "keyword1, keyword2, keyword3"
|
|
3801
|
+
}
|
|
3802
|
+
),
|
|
3803
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Comma-separated keywords for SEO" })
|
|
3804
|
+
] }),
|
|
3805
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
3806
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "display-order", children: "Display Order" }),
|
|
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
|
|
3906
3834
|
}
|
|
3907
|
-
)
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
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;
|
|
3886
|
+
}
|
|
3887
|
+
toast.error("Failed to update coupon", {
|
|
3888
|
+
description: message
|
|
3889
|
+
});
|
|
3890
|
+
}
|
|
3891
|
+
};
|
|
3892
|
+
return /* @__PURE__ */ jsxs(Container, { children: [
|
|
3893
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
3894
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3895
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold mb-1", children: campaign.name }),
|
|
3896
|
+
/* @__PURE__ */ jsx("p", { className: "text-ui-fg-subtle", children: "Manage coupon configuration and content" })
|
|
3897
|
+
] }),
|
|
3898
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3899
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/coupons"), children: "Back to Coupons" }),
|
|
3900
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
|
|
3901
|
+
] })
|
|
3902
|
+
] }),
|
|
3903
|
+
/* @__PURE__ */ jsxs(Tabs, { defaultValue: "settings", children: [
|
|
3904
|
+
/* @__PURE__ */ jsxs(Tabs.List, { children: [
|
|
3905
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "settings", children: "Coupon Settings" }),
|
|
3906
|
+
/* @__PURE__ */ jsx(Tabs.Trigger, { value: "details", children: "Coupon Details" })
|
|
3907
|
+
] }),
|
|
3908
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
3909
|
+
CouponForm,
|
|
3912
3910
|
{
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3911
|
+
initialData: initialValues,
|
|
3912
|
+
onSubmit: handleSubmit,
|
|
3913
|
+
onCancel: () => navigate("/coupons"),
|
|
3914
|
+
disabled: !isEditing
|
|
3915
|
+
}
|
|
3916
|
+
) }),
|
|
3917
|
+
/* @__PURE__ */ jsx(Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsx(
|
|
3918
|
+
CampaignDetailForm,
|
|
3919
|
+
{
|
|
3920
|
+
campaignId: campaign.id,
|
|
3921
|
+
campaignName: campaign.name ?? ""
|
|
3924
3922
|
}
|
|
3925
3923
|
) })
|
|
3926
|
-
] })
|
|
3924
|
+
] })
|
|
3927
3925
|
] });
|
|
3928
3926
|
};
|
|
3927
|
+
const config$1 = defineRouteConfig({
|
|
3928
|
+
label: "Coupon Detail",
|
|
3929
|
+
icon: Tag
|
|
3930
|
+
});
|
|
3931
|
+
const CouponCreate = () => {
|
|
3932
|
+
const navigate = useNavigate();
|
|
3933
|
+
const handleSubmit = async (formData) => {
|
|
3934
|
+
var _a, _b;
|
|
3935
|
+
try {
|
|
3936
|
+
await axios.post("/admin/coupons", {
|
|
3937
|
+
...formData,
|
|
3938
|
+
starts_at: new Date(formData.starts_at).toUTCString(),
|
|
3939
|
+
ends_at: new Date(formData.ends_at).toUTCString(),
|
|
3940
|
+
currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
|
|
3941
|
+
});
|
|
3942
|
+
toast.success("Coupon created successfully");
|
|
3943
|
+
navigate("/coupons");
|
|
3944
|
+
} catch (error) {
|
|
3945
|
+
let message = "Failed to create coupon";
|
|
3946
|
+
if (axios.isAxiosError(error)) {
|
|
3947
|
+
message = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) ?? message;
|
|
3948
|
+
} else if (error instanceof Error) {
|
|
3949
|
+
message = error.message;
|
|
3950
|
+
}
|
|
3951
|
+
toast.error("Failed to create coupon", {
|
|
3952
|
+
description: message
|
|
3953
|
+
});
|
|
3954
|
+
}
|
|
3955
|
+
};
|
|
3956
|
+
return /* @__PURE__ */ jsx(
|
|
3957
|
+
CouponForm,
|
|
3958
|
+
{
|
|
3959
|
+
onSubmit: handleSubmit,
|
|
3960
|
+
onCancel: () => navigate("/coupons")
|
|
3961
|
+
}
|
|
3962
|
+
);
|
|
3963
|
+
};
|
|
3929
3964
|
const useFlashSaleById = (id) => {
|
|
3930
3965
|
const [data, setData] = useState(null);
|
|
3931
3966
|
const [isLoading, setIsLoading] = useState(true);
|
|
@@ -4044,41 +4079,6 @@ const config = defineRouteConfig({
|
|
|
4044
4079
|
label: "Flash Sale Detail",
|
|
4045
4080
|
icon: Sparkles
|
|
4046
4081
|
});
|
|
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,13 +4087,17 @@ const widgetModule = { widgets: [
|
|
|
4087
4087
|
] };
|
|
4088
4088
|
const routeModule = {
|
|
4089
4089
|
routes: [
|
|
4090
|
+
{
|
|
4091
|
+
Component: FlashSale,
|
|
4092
|
+
path: "/flash-sales"
|
|
4093
|
+
},
|
|
4090
4094
|
{
|
|
4091
4095
|
Component: Coupons,
|
|
4092
4096
|
path: "/coupons"
|
|
4093
4097
|
},
|
|
4094
4098
|
{
|
|
4095
|
-
Component:
|
|
4096
|
-
path: "/flash-sales"
|
|
4099
|
+
Component: FlashSaleCreate,
|
|
4100
|
+
path: "/flash-sales/create"
|
|
4097
4101
|
},
|
|
4098
4102
|
{
|
|
4099
4103
|
Component: CouponDetail,
|
|
@@ -4106,24 +4110,20 @@ const routeModule = {
|
|
|
4106
4110
|
{
|
|
4107
4111
|
Component: FlashSaleDetail,
|
|
4108
4112
|
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$2.label,
|
|
4120
|
+
icon: config$2.icon,
|
|
4121
4121
|
path: "/coupons",
|
|
4122
4122
|
nested: void 0
|
|
4123
4123
|
},
|
|
4124
4124
|
{
|
|
4125
|
-
label: config$
|
|
4126
|
-
icon: config$
|
|
4125
|
+
label: config$3.label,
|
|
4126
|
+
icon: config$3.icon,
|
|
4127
4127
|
path: "/flash-sales",
|
|
4128
4128
|
nested: void 0
|
|
4129
4129
|
},
|