@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.
@@ -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, Label, Input, Textarea, Select, toast, Tabs, clx, Table, FocusModal } from "@medusajs/ui";
4
- import { Sparkles, PhotoSolid, PencilSquare, Eye, Tag, InformationCircle, CommandLine, X, CloudArrowUp, Trash } from "@medusajs/icons";
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 useCoupons = ({ limit, offset }) => {
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 fetchCoupons = async () => {
201
+ const fetchFlashSales = async () => {
202
202
  setIsLoading(true);
203
203
  setError(null);
204
204
  try {
205
- const response = await axios.get("/admin/coupons", {
206
- params: { limit, offset }
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(err instanceof Error ? err : new Error("Failed to fetch coupons"));
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
- fetchCoupons();
217
- }, [limit, offset]);
218
+ fetchFlashSales();
219
+ }, [pagination.limit, pagination.offset]);
218
220
  return {
219
221
  data,
220
222
  isLoading,
221
223
  error,
222
- refetch: fetchCoupons
224
+ refetch: fetchFlashSales
223
225
  };
224
226
  };
225
- const columnHelper$1 = createDataTableColumnHelper();
226
- const useCouponColumns = () => useMemo(
227
- () => [
228
- columnHelper$1.accessor("name", {
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
- columnHelper$1.display({
233
- id: "code",
234
- header: "Code",
235
- cell: (info) => {
236
- var _a, _b;
237
- return ((_b = (_a = info.row.original.promotions) == null ? void 0 : _a[0]) == null ? void 0 : _b.code) ?? "-";
238
- }
239
- }),
240
- columnHelper$1.display({
241
- id: "status",
243
+ columnHelper2.accessor("ends_at", {
242
244
  header: "Status",
243
245
  cell: (info) => {
244
- const endsAt = info.row.original.ends_at;
245
- if (!endsAt) {
246
+ const date = info.getValue();
247
+ if (!date) {
246
248
  return "";
247
249
  }
248
- const isActive = dayjs(endsAt).isAfter(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
- columnHelper$1.accessor("starts_at", {
254
+ columnHelper2.accessor("starts_at", {
253
255
  header: "Start Date",
254
- cell: (info) => info.getValue() ? dayjs(info.getValue()).format("YYYY-MM-DD HH:mm") : ""
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
- columnHelper$1.accessor("ends_at", {
264
+ columnHelper2.accessor("ends_at", {
257
265
  header: "End Date",
258
- cell: (info) => info.getValue() ? dayjs(info.getValue()).format("YYYY-MM-DD HH:mm") : ""
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: (row) => row.id,
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) ?? 0,
284
+ rowCount: (data == null ? void 0 : data.count) || 0,
282
285
  onRowClick: (_, row) => {
283
- navigate(`/coupons/${row.id}`);
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: "Coupons" }),
289
- /* @__PURE__ */ jsx(Button, { onClick: () => navigate("/coupons/create"), children: "Create Coupon" })
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 Coupons = () => {
298
- return /* @__PURE__ */ jsx(CouponPage, {});
300
+ const FlashSale = () => {
301
+ return /* @__PURE__ */ jsx(FlashSalePage, {});
299
302
  };
300
303
  const config$3 = defineRouteConfig({
301
- label: "Coupons",
302
- icon: Tag
304
+ label: "Flash Sale",
305
+ icon: Sparkles
303
306
  });
304
- const useFlashSales = (pagination) => {
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 fetchFlashSales = async () => {
311
+ const fetchCoupons = async () => {
309
312
  setIsLoading(true);
310
313
  setError(null);
311
314
  try {
312
- const response = await axios.get("/admin/flash-sales", {
313
- params: pagination
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
- fetchFlashSales();
326
- }, [pagination.limit, pagination.offset]);
326
+ fetchCoupons();
327
+ }, [limit, offset]);
327
328
  return {
328
329
  data,
329
330
  isLoading,
330
331
  error,
331
- refetch: fetchFlashSales
332
+ refetch: fetchCoupons
332
333
  };
333
334
  };
334
- const FlashSalePage = () => {
335
- const navigate = useNavigate();
336
- const limit = 20;
337
- const [pagination, setPagination] = useState({
338
- pageSize: limit,
339
- pageIndex: 0
340
- });
341
- const offset = useMemo(() => {
342
- return pagination.pageIndex * limit;
343
- }, [pagination]);
344
- const columnHelper2 = createDataTableColumnHelper();
345
- const columns2 = [
346
- columnHelper2.accessor("name", {
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
- columnHelper2.accessor("ends_at", {
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 date = info.getValue();
354
- if (!date) {
354
+ const endsAt = info.row.original.ends_at;
355
+ if (!endsAt) {
355
356
  return "";
356
357
  }
357
- const isActive = dayjs(date).isAfter(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
- columnHelper2.accessor("starts_at", {
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
- columnHelper2.accessor("ends_at", {
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
- const { data } = useFlashSales({ limit, offset });
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: (campaign) => campaign.id,
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) || 0,
391
+ rowCount: (data == null ? void 0 : data.count) ?? 0,
392
392
  onRowClick: (_, row) => {
393
- navigate(`/flash-sales/${row.id}`);
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: "Campaigns" }),
399
- /* @__PURE__ */ jsx(Button, { onClick: () => navigate("/flash-sales/create"), children: "Create Campaign" })
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 FlashSale = () => {
408
- return /* @__PURE__ */ jsx(FlashSalePage, {});
407
+ const Coupons = () => {
408
+ return /* @__PURE__ */ jsx(CouponPage, {});
409
409
  };
410
410
  const config$2 = defineRouteConfig({
411
- label: "Flash Sale",
412
- icon: Sparkles
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 couponSchema = z.object({
2296
- name: z.string().min(1, "Name is required"),
2297
- description: z.string().min(1, "Description is required"),
2298
- type: z.literal("coupon"),
2299
- code: z.string().min(1, "Coupon code is required"),
2300
- discount_type: z.enum(["percentage", "fixed"]),
2301
- discount_value: z.number().positive("Discount must be positive"),
2302
- currency_code: z.string().optional(),
2303
- starts_at: z.string().min(1, "Start date is required"),
2304
- ends_at: z.string().min(1, "End date is required"),
2305
- allocation: z.enum(["each", "total"]).optional(),
2306
- target_type: z.enum(["order", "items"]).optional()
2307
- }).superRefine((data, ctx) => {
2308
- if (new Date(data.ends_at) < new Date(data.starts_at)) {
2309
- ctx.addIssue({
2310
- code: z.ZodIssueCode.custom,
2311
- path: ["ends_at"],
2312
- message: "End date must be after start date"
2313
- });
2314
- }
2315
- if (data.discount_type === "fixed" && !data.currency_code) {
2316
- ctx.addIssue({
2317
- code: z.ZodIssueCode.custom,
2318
- path: ["currency_code"],
2319
- message: "Currency is required for fixed discount"
2320
- });
2295
+ const sdk = new Medusa({
2296
+ baseUrl: "/",
2297
+ debug: false,
2298
+ auth: {
2299
+ type: "session"
2321
2300
  }
2322
2301
  });
2323
- const CouponForm = ({
2324
- initialData,
2325
- onSubmit,
2326
- onCancel,
2327
- disabled = false
2328
- }) => {
2329
- const {
2330
- register,
2331
- control,
2332
- handleSubmit,
2333
- watch,
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(couponSchema),
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: "coupon",
2342
- code: (initialData == null ? void 0 : initialData.code) ?? "",
2343
- discount_type: (initialData == null ? void 0 : initialData.discount_type) ?? "percentage",
2344
- discount_value: (initialData == null ? void 0 : initialData.discount_value) ?? 0,
2345
- currency_code: (initialData == null ? void 0 : initialData.currency_code) ?? "",
2346
- starts_at: (initialData == null ? void 0 : initialData.starts_at) ?? dayjs().startOf("day").format("YYYY-MM-DDTHH:mm"),
2347
- ends_at: (initialData == null ? void 0 : initialData.ends_at) ?? dayjs().endOf("day").format("YYYY-MM-DDTHH:mm"),
2348
- allocation: (initialData == null ? void 0 : initialData.allocation) ?? "total",
2349
- target_type: (initialData == null ? void 0 : initialData.target_type) ?? "order"
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 discountType = watch("discount_type");
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.values(errors).map((err) => err == null ? void 0 : err.message).filter(Boolean).join(", ");
2370
- toast.error("Invalid coupon data", {
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: "Coupon" }),
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-6 my-8",
2461
+ className: "space-y-4 my-8",
2384
2462
  children: [
2385
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
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", { className: "space-y-2", children: [
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", { className: "space-y-2", children: [
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: "flex-1"
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", { className: "space-y-2", children: [
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: "flex-1"
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: "grid grid-cols-2 gap-4", children: [
2450
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2451
- /* @__PURE__ */ jsx(Label, { children: "Coupon Code" }),
2452
- /* @__PURE__ */ jsx(Input, { ...register("code"), disabled }),
2453
- errors.code && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.code.message })
2454
- ] }),
2455
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2456
- /* @__PURE__ */ jsx(Label, { children: "Discount Type" }),
2457
- /* @__PURE__ */ jsx(
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: "discount_type",
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: field.value,
2466
- onValueChange: field.onChange,
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__ */ jsxs(Select.Content, { children: [
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: "discount_value",
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: field.value,
2494
- min: 0,
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
- errors.discount_value && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.discount_value.message })
2503
- ] }),
2504
- discountType === "fixed" && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2505
- /* @__PURE__ */ jsx(Label, { children: "Currency Code" }),
2506
- /* @__PURE__ */ jsx(Input, { ...register("currency_code"), disabled }),
2507
- errors.currency_code && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.currency_code.message })
2508
- ] })
2509
- ] }),
2510
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
2511
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2512
- /* @__PURE__ */ jsx(Label, { children: "Allocation" }),
2513
- /* @__PURE__ */ jsx(
2586
+ ) }),
2587
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
2514
2588
  Controller,
2515
2589
  {
2516
- name: "allocation",
2590
+ name: `products.${index}.limit`,
2517
2591
  control,
2518
- render: ({ field }) => /* @__PURE__ */ jsxs(
2519
- Select,
2592
+ render: ({ field: field2 }) => /* @__PURE__ */ jsx(
2593
+ Input,
2520
2594
  {
2521
- value: field.value,
2522
- onValueChange: field.onChange,
2523
- disabled,
2524
- children: [
2525
- /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select allocation" }) }),
2526
- /* @__PURE__ */ jsxs(Select.Content, { children: [
2527
- /* @__PURE__ */ jsx(Select.Item, { value: "total", children: "Order Total" }),
2528
- /* @__PURE__ */ jsx(Select.Item, { value: "each", children: "Each Item" })
2529
- ] })
2530
- ]
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: "target_type",
2606
+ name: `products.${index}.maxQty`,
2542
2607
  control,
2543
- render: ({ field }) => /* @__PURE__ */ jsxs(
2544
- Select,
2608
+ render: ({ field: field2 }) => /* @__PURE__ */ jsx(
2609
+ Input,
2545
2610
  {
2546
- value: field.value,
2547
- onValueChange: field.onChange,
2548
- disabled,
2549
- children: [
2550
- /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select target" }) }),
2551
- /* @__PURE__ */ jsxs(Select.Content, { children: [
2552
- /* @__PURE__ */ jsx(Select.Item, { value: "order", children: "Order" }),
2553
- /* @__PURE__ */ jsx(Select.Item, { value: "items", children: "Items" })
2554
- ] })
2555
- ]
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__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
2563
- /* @__PURE__ */ jsx(Button, { variant: "secondary", type: "button", onClick: onCancel, children: "Cancel" }),
2564
- /* @__PURE__ */ jsx(Button, { type: "submit", disabled, children: "Save Coupon" })
2565
- ] })
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 useCouponById = (id) => {
2572
- const [data, setData] = useState(null);
2573
- const [isLoading, setIsLoading] = useState(true);
2574
- const [error, setError] = useState(null);
2575
- const fetchCoupon = async () => {
2576
- if (!id) {
2577
- return;
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
- const response = await axios.get(`/admin/coupons/${id}`);
2583
- setData(response.data);
2584
- } catch (err) {
2585
- setError(err instanceof Error ? err : new Error("Failed to fetch coupon"));
2586
- } finally {
2587
- setIsLoading(false);
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
- useEffect(() => {
2591
- fetchCoupon();
2592
- }, [id]);
2593
- return {
2594
- data,
2595
- isLoading,
2596
- error,
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 MarkdownEditor = ({
2601
- label,
2602
- value,
2603
- onChange,
2604
- placeholder,
2605
- helpText,
2606
- rows = 10,
2607
- showPreview = true
2691
+ const couponSchema = z.object({
2692
+ name: z.string().min(1, "Name is required"),
2693
+ description: z.string().min(1, "Description is required"),
2694
+ type: z.literal("coupon"),
2695
+ code: z.string().min(1, "Coupon code is required"),
2696
+ discount_type: z.enum(["percentage", "fixed"]),
2697
+ discount_value: z.number().positive("Discount must be positive"),
2698
+ currency_code: z.string().optional(),
2699
+ starts_at: z.string().min(1, "Start date is required"),
2700
+ ends_at: z.string().min(1, "End date is required"),
2701
+ allocation: z.enum(["each", "total"]).optional(),
2702
+ target_type: z.enum(["order", "items"]).optional()
2703
+ }).superRefine((data, ctx) => {
2704
+ if (new Date(data.ends_at) < new Date(data.starts_at)) {
2705
+ ctx.addIssue({
2706
+ code: z.ZodIssueCode.custom,
2707
+ path: ["ends_at"],
2708
+ message: "End date must be after start date"
2709
+ });
2710
+ }
2711
+ if (data.discount_type === "fixed" && !data.currency_code) {
2712
+ ctx.addIssue({
2713
+ code: z.ZodIssueCode.custom,
2714
+ path: ["currency_code"],
2715
+ message: "Currency is required for fixed discount"
2716
+ });
2717
+ }
2718
+ });
2719
+ const CouponForm = ({
2720
+ initialData,
2721
+ onSubmit,
2722
+ onCancel,
2723
+ disabled = false
2608
2724
  }) => {
2609
- const [activeTab, setActiveTab] = useState("write");
2610
- const insertMarkdown = (before, after = "") => {
2611
- const textarea = document.getElementById(
2612
- `markdown-${label}`
2613
- );
2614
- if (!textarea) return;
2615
- const start = textarea.selectionStart;
2616
- const end = textarea.selectionEnd;
2617
- const selectedText = value.substring(start, end);
2618
- const newText = value.substring(0, start) + before + selectedText + after + value.substring(end);
2619
- onChange(newText);
2620
- setTimeout(() => {
2621
- textarea.focus();
2622
- textarea.setSelectionRange(
2623
- start + before.length,
2624
- start + before.length + selectedText.length
2625
- );
2626
- }, 0);
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 renderMarkdownPreview = (markdown) => {
2629
- let html = markdown;
2630
- html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
2631
- html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
2632
- html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
2633
- html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
2634
- html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
2635
- html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
2636
- html = html.replace(/_(.+?)_/g, "<em>$1</em>");
2637
- html = html.replace(
2638
- /```(\w+)?\n([\s\S]+?)```/g,
2639
- '<pre class="bg-ui-bg-subtle p-4 rounded-lg overflow-x-auto"><code class="language-$1">$2</code></pre>'
2640
- );
2641
- html = html.replace(
2642
- /`([^`]+)`/g,
2643
- '<code class="bg-ui-bg-subtle px-1 py-0.5 rounded text-sm">$1</code>'
2644
- );
2645
- html = html.replace(
2646
- /\[([^\]]+)\]\(([^)]+)\)/g,
2647
- '<a href="$2" class="text-ui-fg-interactive underline">$1</a>'
2648
- );
2649
- html = html.replace(
2650
- /!\[([^\]]*)\]\(([^)]+)\)/g,
2651
- '<img src="$2" alt="$1" class="max-w-full h-auto rounded-lg my-2" />'
2652
- );
2653
- html = html.replace(/^\* (.+)$/gim, "<li>$1</li>");
2654
- html = html.replace(/^\- (.+)$/gim, "<li>$1</li>");
2655
- html = html.replace(
2656
- /(<li>.*<\/li>)/s,
2657
- "<ul class='list-disc ml-6'>$1</ul>"
2658
- );
2659
- html = html.replace(/\n\n/g, "</p><p>");
2660
- html = `<p>${html}</p>`;
2661
- return html;
2761
+ const onFormSubmit = (data) => {
2762
+ onSubmit(data);
2662
2763
  };
2663
- return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
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(Label, { htmlFor: `markdown-${label}`, className: "font-medium", children: label }),
2666
- helpText && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-xs text-ui-fg-subtle", children: [
2667
- /* @__PURE__ */ jsx(InformationCircle, { className: "h-4 w-4" }),
2668
- /* @__PURE__ */ jsx("span", { children: helpText })
2669
- ] })
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("![alt](", ")"),
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__ */ jsx("div", { className: "text-xs text-ui-fg-subtle", children: "Supports Markdown formatting: **bold**, *italic*, `code`, ```code blocks```, [links](url), ![images](url), and lists" })
2862
- ] });
2863
- };
2864
- const CampaignImageUploader = ({
2865
- campaignId,
2866
- imageType,
2867
- currentImageUrl,
2868
- onClose,
2869
- onSuccess
2870
- }) => {
2871
- const [displayImage, setDisplayImage] = useState(currentImageUrl || null);
2872
- const [isUploading, setIsUploading] = useState(false);
2873
- const [isDragging, setIsDragging] = useState(false);
2874
- const [error, setError] = useState(null);
2875
- const [previewUrl, setPreviewUrl] = useState(null);
2876
- const isThumbnail = imageType === "thumbnail";
2877
- const maxSize = 5;
2878
- const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
2879
- const formatFileTypes = () => {
2880
- return "JPEG, PNG, GIF, or WebP";
2881
- };
2882
- const validateFile = (file) => {
2883
- if (!allowedTypes.includes(file.type)) {
2884
- setError(`Please upload a valid image file (${formatFileTypes()})`);
2885
- return false;
2886
- }
2887
- if (file.size > maxSize * 1024 * 1024) {
2888
- setError(`File size must be less than ${maxSize}MB`);
2889
- return false;
2890
- }
2891
- return true;
2892
- };
2893
- const handleFileUpload = async (file) => {
2894
- var _a, _b;
2895
- if (!validateFile(file)) return;
2896
- setError(null);
2897
- const reader = new FileReader();
2898
- reader.onloadend = () => {
2899
- setPreviewUrl(reader.result);
2900
- };
2901
- reader.readAsDataURL(file);
2902
- setIsUploading(true);
2903
- const formData = new FormData();
2904
- formData.append("file", file);
2905
- try {
2906
- const response = await fetch(
2907
- `/admin/campaigns/${campaignId}/${imageType}`,
2908
- {
2909
- method: "POST",
2910
- body: formData,
2911
- credentials: "include"
2912
- }
2913
- );
2914
- if (response.ok) {
2915
- const result = await response.json();
2916
- const newImageUrl = imageType === "image" ? (_a = result.campaign_detail) == null ? void 0 : _a.image_url : (_b = result.campaign_detail) == null ? void 0 : _b.thumbnail_url;
2917
- setDisplayImage(newImageUrl);
2918
- setPreviewUrl(null);
2919
- onSuccess == null ? void 0 : onSuccess();
2920
- setTimeout(() => {
2921
- onClose();
2922
- }, 1e3);
2923
- } else {
2924
- const errorData = await response.json();
2925
- setError(errorData.error || `Failed to upload ${imageType}`);
2926
- setPreviewUrl(null);
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
- } catch (err) {
2929
- setError(`Error uploading ${imageType}`);
2930
- setPreviewUrl(null);
2931
- console.error(`Error uploading ${imageType}:`, err);
2932
- } finally {
2933
- setIsUploading(false);
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 fetch(
2942
- `/admin/campaigns/${campaignId}/${imageType}`,
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(`Error deleting ${imageType}`);
2961
- console.error(`Error deleting ${imageType}:`, err);
2981
+ setError(err instanceof Error ? err : new Error("Failed to fetch coupon"));
2962
2982
  } finally {
2963
- setIsUploading(false);
2983
+ setIsLoading(false);
2964
2984
  }
2965
2985
  };
2966
- const handleDragEnter = useCallback((e2) => {
2967
- e2.preventDefault();
2968
- e2.stopPropagation();
2969
- setIsDragging(true);
2970
- }, []);
2971
- const handleDragLeave = useCallback((e2) => {
2972
- e2.preventDefault();
2973
- e2.stopPropagation();
2974
- setIsDragging(false);
2975
- }, []);
2976
- const handleDragOver = useCallback((e2) => {
2977
- e2.preventDefault();
2978
- e2.stopPropagation();
2979
- }, []);
2980
- const handleDrop = useCallback((e2) => {
2981
- var _a;
2982
- e2.preventDefault();
2983
- e2.stopPropagation();
2984
- setIsDragging(false);
2985
- const file = (_a = e2.dataTransfer.files) == null ? void 0 : _a[0];
2986
- if (file) {
2987
- handleFileUpload(file);
2988
- }
2989
- }, []);
2990
- const imageToDisplay = previewUrl || displayImage;
2991
- return /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-2xl rounded-lg bg-ui-bg-base p-6", children: [
2992
- /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
2993
- /* @__PURE__ */ jsxs("div", { children: [
2994
- /* @__PURE__ */ jsxs(Heading, { level: "h2", children: [
2995
- "Upload Campaign ",
2996
- isThumbnail ? "Thumbnail" : "Image"
2997
- ] }),
2998
- /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: isThumbnail ? "Thumbnail for campaign listing" : "Main campaign image" })
2999
- ] }),
3000
- /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsx(X, {}) })
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
- error && /* @__PURE__ */ jsx(Alert, { variant: "error", dismissible: true, className: "mb-4", children: error }),
3003
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
3004
- imageToDisplay ? /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
3005
- /* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-subtle", children: [
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
- "img",
3075
+ Button,
3008
3076
  {
3009
- src: imageToDisplay,
3010
- alt: `Campaign ${imageType}`,
3011
- className: clx(
3012
- "w-full object-contain",
3013
- isThumbnail ? "h-32" : "h-64"
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
- isUploading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsx("div", { className: "text-white", children: "Uploading..." }) })
3018
- ] }),
3019
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
3020
- /* @__PURE__ */ jsxs(
3085
+ /* @__PURE__ */ jsx(
3021
3086
  Button,
3022
3087
  {
3088
+ type: "button",
3023
3089
  variant: "secondary",
3024
- disabled: isUploading,
3025
- onClick: () => {
3026
- var _a;
3027
- return (_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click();
3028
- },
3029
- children: [
3030
- /* @__PURE__ */ jsx(CloudArrowUp, { className: "mr-2" }),
3031
- "Replace"
3032
- ]
3090
+ size: "small",
3091
+ onClick: () => insertMarkdown("*", "*"),
3092
+ title: "Italic",
3093
+ children: /* @__PURE__ */ jsx("em", { children: "I" })
3033
3094
  }
3034
3095
  ),
3035
- /* @__PURE__ */ jsxs(
3096
+ /* @__PURE__ */ jsx(
3036
3097
  Button,
3037
3098
  {
3038
- variant: "danger",
3039
- disabled: isUploading,
3040
- onClick: handleDelete,
3041
- children: [
3042
- /* @__PURE__ */ jsx(Trash, { className: "mr-2" }),
3043
- "Delete"
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
- onDragEnter: handleDragEnter,
3058
- onDragLeave: handleDragLeave,
3059
- onDragOver: handleDragOver,
3060
- onDrop: handleDrop,
3061
- onClick: () => {
3062
- var _a;
3063
- return !isUploading && ((_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click());
3064
- },
3065
- children: [
3066
- /* @__PURE__ */ jsx(PhotoSolid, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }),
3067
- /* @__PURE__ */ jsx(Text, { className: "mb-2 text-lg font-medium", children: isDragging ? `Drop ${imageType} here` : `Upload ${isThumbnail ? "Thumbnail" : "Image"}` }),
3068
- /* @__PURE__ */ jsx(Text, { className: "mb-4 text-center text-ui-fg-subtle", children: "Drag and drop an image here, or click to select" }),
3069
- /* @__PURE__ */ jsxs(Text, { className: "text-sm text-ui-fg-subtle", children: [
3070
- formatFileTypes(),
3071
- " • Max ",
3072
- maxSize,
3073
- "MB"
3074
- ] }),
3075
- isThumbnail && /* @__PURE__ */ jsx(Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: 16:9 aspect ratio, minimum 400x225px" }),
3076
- !isThumbnail && /* @__PURE__ */ jsx(Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: High resolution, minimum 1200x600px" }),
3077
- isUploading && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(Text, { children: "Uploading..." }) })
3078
- ]
3079
- }
3080
- ),
3081
- /* @__PURE__ */ jsx(
3082
- "input",
3083
- {
3084
- id: `${imageType}-file-input`,
3085
- type: "file",
3086
- accept: allowedTypes.join(","),
3087
- onChange: (e2) => {
3088
- var _a;
3089
- const file = (_a = e2.target.files) == null ? void 0 : _a[0];
3090
- if (file) handleFileUpload(file);
3091
- e2.target.value = "";
3092
- },
3093
- className: "hidden"
3094
- }
3095
- )
3096
- ] }),
3097
- /* @__PURE__ */ jsxs("div", { className: "mt-6 flex items-center justify-between border-t pt-4", children: [
3098
- /* @__PURE__ */ jsx(Text, { className: "text-sm text-ui-fg-subtle", children: isThumbnail ? "Thumbnail will be displayed in campaign listings" : "Main image for the campaign detail page" }),
3099
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: "Close" })
3100
- ] })
3101
- ] }) });
3102
- };
3103
- const CampaignDetailForm = ({
3104
- campaignId,
3105
- campaignName
3106
- }) => {
3107
- const [campaignDetail, setCampaignDetail] = useState(
3108
- null
3109
- );
3110
- const [isLoading, setIsLoading] = useState(true);
3111
- const [isSaving, setIsSaving] = useState(false);
3112
- const [showImageUploader, setShowImageUploader] = useState(null);
3113
- const [formData, setFormData] = useState({
3114
- detail_content: "",
3115
- terms_and_conditions: "",
3116
- meta_title: "",
3117
- meta_description: "",
3118
- meta_keywords: "",
3119
- link_url: "",
3120
- link_text: "",
3121
- display_order: 0
3122
- });
3123
- useEffect(() => {
3124
- fetchCampaignDetail();
3125
- }, [campaignId]);
3126
- const fetchCampaignDetail = async () => {
3127
- setIsLoading(true);
3128
- try {
3129
- const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
3130
- credentials: "include"
3131
- });
3132
- if (response.ok) {
3133
- const data = await response.json();
3134
- if (data.campaign_detail) {
3135
- console.log("Campaign detail loaded:", {
3136
- image_url: data.campaign_detail.image_url,
3137
- thumbnail_url: data.campaign_detail.thumbnail_url,
3138
- decoded_image: data.campaign_detail.image_url ? decodeURIComponent(data.campaign_detail.image_url) : null,
3139
- decoded_thumbnail: data.campaign_detail.thumbnail_url ? decodeURIComponent(data.campaign_detail.thumbnail_url) : null
3140
- });
3141
- setCampaignDetail(data.campaign_detail);
3142
- setFormData({
3143
- detail_content: data.campaign_detail.detail_content || "",
3144
- terms_and_conditions: data.campaign_detail.terms_and_conditions || "",
3145
- meta_title: data.campaign_detail.meta_title || "",
3146
- meta_description: data.campaign_detail.meta_description || "",
3147
- meta_keywords: data.campaign_detail.meta_keywords || "",
3148
- link_url: data.campaign_detail.link_url || "",
3149
- link_text: data.campaign_detail.link_text || "",
3150
- display_order: data.campaign_detail.display_order || 0
3151
- });
3152
- }
3153
- } else if (response.status !== 404) {
3154
- console.error("Failed to fetch campaign detail");
3155
- }
3156
- } catch (error) {
3157
- console.error("Error fetching campaign detail:", error);
3158
- } finally {
3159
- setIsLoading(false);
3160
- }
3161
- };
3162
- const handleSave = async () => {
3163
- setIsSaving(true);
3164
- try {
3165
- const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
3166
- method: "POST",
3167
- headers: {
3168
- "Content-Type": "application/json"
3169
- },
3170
- body: JSON.stringify(formData),
3171
- credentials: "include"
3172
- });
3173
- if (response.ok) {
3174
- const data = await response.json();
3175
- setCampaignDetail(data.campaign_detail);
3176
- toast.success("Campaign details saved successfully");
3177
- } else {
3178
- const errorData = await response.json();
3179
- toast.error(errorData.error || "Failed to save campaign details");
3180
- }
3181
- } catch (error) {
3182
- console.error("Error saving campaign detail:", error);
3183
- toast.error("Error saving campaign details");
3184
- } finally {
3185
- setIsSaving(false);
3186
- }
3187
- };
3188
- if (isLoading) {
3189
- return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { className: "py-8 text-center", children: "Loading campaign details..." }) });
3190
- }
3191
- return /* @__PURE__ */ jsxs(Fragment, { children: [
3192
- /* @__PURE__ */ jsxs(Container, { className: "space-y-6", children: [
3193
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
3194
- /* @__PURE__ */ jsxs("div", { children: [
3195
- /* @__PURE__ */ jsx(Heading, { level: "h2", children: "Campaign Details" }),
3196
- /* @__PURE__ */ jsx("p", { className: "text-sm text-ui-fg-subtle", children: campaignName })
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("![alt](", ")"),
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("div", { className: "flex justify-end border-t pt-4", children: /* @__PURE__ */ jsx(Button, { onClick: handleSave, isLoading: isSaving, children: "Save Campaign Details" }) })
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
- showImageUploader && /* @__PURE__ */ jsx(
3431
- CampaignImageUploader,
3432
- {
3433
- campaignId,
3434
- imageType: showImageUploader,
3435
- currentImageUrl: showImageUploader === "image" ? campaignDetail == null ? void 0 : campaignDetail.image_url : campaignDetail == null ? void 0 : campaignDetail.thumbnail_url,
3436
- onClose: () => setShowImageUploader(null),
3437
- onSuccess: fetchCampaignDetail
3438
- }
3439
- )
3257
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-ui-fg-subtle", children: "Supports Markdown formatting: **bold**, *italic*, `code`, ```code blocks```, [links](url), ![images](url), and lists" })
3440
3258
  ] });
3441
3259
  };
3442
- const CouponDetail = () => {
3443
- var _a, _b, _c, _d, _e, _f;
3444
- const { id } = useParams();
3445
- const navigate = useNavigate();
3446
- const [isEditing, setIsEditing] = useState(false);
3447
- const { data, isLoading, refetch } = useCouponById(id ?? "");
3448
- if (isLoading) {
3449
- return /* @__PURE__ */ jsx(Container, { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsx("div", { className: "animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent" }) });
3450
- }
3451
- if (!(data == null ? void 0 : data.campaign)) {
3452
- return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("p", { children: "Coupon not found" }) });
3453
- }
3454
- const campaign = data.campaign;
3455
- const promotion = (_a = campaign.promotions) == null ? void 0 : _a[0];
3456
- const initialValues = {
3457
- name: campaign.name ?? "",
3458
- description: campaign.description ?? "",
3459
- type: "coupon",
3460
- code: (promotion == null ? void 0 : promotion.code) ?? "",
3461
- discount_type: ((_b = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _b.type) ?? "percentage",
3462
- discount_value: Number(((_c = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _c.value) ?? 0),
3463
- currency_code: ((_d = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _d.currency_code) ?? void 0,
3464
- starts_at: campaign.starts_at ? dayjs(campaign.starts_at).format("YYYY-MM-DDTHH:mm") : void 0,
3465
- ends_at: campaign.ends_at ? dayjs(campaign.ends_at).format("YYYY-MM-DDTHH:mm") : void 0,
3466
- allocation: ((_e = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _e.allocation) ?? "total",
3467
- target_type: ((_f = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _f.target_type) ?? "order"
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 handleSubmit = async (formData) => {
3470
- var _a2, _b2;
3471
- if (!id) {
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 axios.put(`/admin/coupons/${id}`, {
3476
- ...formData,
3477
- starts_at: new Date(formData.starts_at).toUTCString(),
3478
- ends_at: new Date(formData.ends_at).toUTCString(),
3479
- currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
3480
- });
3481
- toast.success("Coupon updated successfully");
3482
- setIsEditing(false);
3483
- refetch();
3484
- } catch (error) {
3485
- let message = "Failed to update coupon";
3486
- if (axios.isAxiosError(error)) {
3487
- message = ((_b2 = (_a2 = error.response) == null ? void 0 : _a2.data) == null ? void 0 : _b2.message) ?? message;
3488
- } else if (error instanceof Error) {
3489
- message = error.message;
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
- toast.error("Failed to update coupon", {
3492
- description: message
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
- return /* @__PURE__ */ jsxs(Container, { children: [
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__ */ jsx("h1", { className: "text-2xl font-semibold mb-1", children: campaign.name }),
3500
- /* @__PURE__ */ jsx("p", { className: "text-ui-fg-subtle", children: "Manage coupon configuration and content" })
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__ */ jsxs("div", { className: "flex gap-2", children: [
3503
- /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/coupons"), children: "Back to Coupons" }),
3504
- /* @__PURE__ */ jsx(Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
3505
- ] })
3396
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsx(X, {}) })
3506
3397
  ] }),
3507
- /* @__PURE__ */ jsxs(Tabs, { defaultValue: "settings", children: [
3508
- /* @__PURE__ */ jsxs(Tabs.List, { children: [
3509
- /* @__PURE__ */ jsx(Tabs.Trigger, { value: "settings", children: "Coupon Settings" }),
3510
- /* @__PURE__ */ jsx(Tabs.Trigger, { value: "details", children: "Coupon Details" })
3511
- ] }),
3512
- /* @__PURE__ */ jsx(Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsx(
3513
- CouponForm,
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
- initialData: initialValues,
3516
- onSubmit: handleSubmit,
3517
- onCancel: () => navigate("/coupons"),
3518
- disabled: !isEditing
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(Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsx(
3522
- CampaignDetailForm,
3476
+ ),
3477
+ /* @__PURE__ */ jsx(
3478
+ "input",
3523
3479
  {
3524
- campaignId: campaign.id,
3525
- campaignName: campaign.name ?? ""
3526
- }
3527
- ) })
3528
- ] })
3529
- ] });
3530
- };
3531
- const config$1 = defineRouteConfig({
3532
- label: "Coupon Detail",
3533
- icon: Tag
3534
- });
3535
- const CouponCreate = () => {
3536
- const navigate = useNavigate();
3537
- const handleSubmit = async (formData) => {
3538
- var _a, _b;
3539
- try {
3540
- await axios.post("/admin/coupons", {
3541
- ...formData,
3542
- starts_at: new Date(formData.starts_at).toUTCString(),
3543
- ends_at: new Date(formData.ends_at).toUTCString(),
3544
- currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
3545
- });
3546
- toast.success("Coupon created successfully");
3547
- navigate("/coupons");
3548
- } catch (error) {
3549
- let message = "Failed to create coupon";
3550
- if (axios.isAxiosError(error)) {
3551
- message = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) ?? message;
3552
- } else if (error instanceof Error) {
3553
- message = error.message;
3554
- }
3555
- toast.error("Failed to create coupon", {
3556
- description: message
3557
- });
3558
- }
3559
- };
3560
- return /* @__PURE__ */ jsx(
3561
- CouponForm,
3562
- {
3563
- onSubmit: handleSubmit,
3564
- onCancel: () => navigate("/coupons")
3565
- }
3566
- );
3567
- };
3568
- const sdk = new Medusa({
3569
- baseUrl: "/",
3570
- debug: false,
3571
- auth: {
3572
- type: "session"
3573
- }
3574
- });
3575
- const columnHelper = createDataTableColumnHelper();
3576
- const columns = [
3577
- columnHelper.accessor("title", {
3578
- header: "Title",
3579
- cell: (info) => /* @__PURE__ */ jsx(Text, { size: "large", className: "py-2", children: info.getValue() })
3580
- }),
3581
- columnHelper.accessor("description", {
3582
- header: "Description",
3583
- cell: (info) => /* @__PURE__ */ jsx(Text, { size: "large", className: "py-2", children: info.getValue() })
3584
- })
3585
- ];
3586
- const ProductSelector = ({
3587
- selectedProductIds,
3588
- onSelectProduct
3589
- }) => {
3590
- const [search, setSearch] = useState("");
3591
- const debouncedSearchHandler = debounce((value) => {
3592
- setSearch(value);
3593
- }, 500);
3594
- const [products, setProducts] = useState(null);
3595
- const [isLoading, setIsLoading] = useState(true);
3596
- useEffect(() => {
3597
- const fetchProducts = async () => {
3598
- setIsLoading(true);
3599
- try {
3600
- const result = await sdk.admin.product.list({
3601
- q: search,
3602
- limit: 100
3603
- });
3604
- setProducts(result);
3605
- } catch (error) {
3606
- console.error("Failed to fetch products:", error);
3607
- } finally {
3608
- setIsLoading(false);
3609
- }
3610
- };
3611
- fetchProducts();
3612
- }, [selectedProductIds, search]);
3613
- const selectableProducts = useMemo(() => {
3614
- var _a;
3615
- return (_a = products == null ? void 0 : products.products) == null ? void 0 : _a.filter(
3616
- (product) => !selectedProductIds.includes(product.id)
3617
- );
3618
- }, [products == null ? void 0 : products.products, selectedProductIds]);
3619
- const table = useDataTable({
3620
- data: selectableProducts || [],
3621
- columns,
3622
- getRowId: (product) => product.id,
3623
- onRowClick: (_, row) => {
3624
- onSelectProduct == null ? void 0 : onSelectProduct(row.original);
3625
- }
3626
- });
3627
- return /* @__PURE__ */ jsxs(Container, { children: [
3628
- /* @__PURE__ */ jsx(
3629
- Input,
3630
- {
3631
- className: "text-lg py-2",
3632
- placeholder: "Search products...",
3633
- onChange: (e2) => debouncedSearchHandler(e2.target.value)
3634
- }
3635
- ),
3636
- /* @__PURE__ */ jsx(DataTable, { instance: table, children: /* @__PURE__ */ jsx(DataTable.Table, {}) })
3637
- ] });
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 customCampaignSchema = z.object({
3640
- name: z.string().min(1, "Name is required"),
3641
- description: z.string().min(1, "Description is required"),
3642
- type: z.literal("flash-sale"),
3643
- starts_at: z.string().min(1, "Start date is required"),
3644
- ends_at: z.string().min(1, "End date is required")
3645
- });
3646
- const campaignProductSchema = z.object({
3647
- product: z.object({
3648
- id: z.string(),
3649
- title: z.string()
3650
- }),
3651
- discountType: z.enum([
3652
- "percentage"
3653
- // , "fixed"
3654
- ]),
3655
- discountValue: z.number().min(1),
3656
- limit: z.number().min(1),
3657
- maxQty: z.number().min(1)
3658
- });
3659
- const campaignDataSchema = customCampaignSchema.extend({
3660
- products: z.array(campaignProductSchema).min(1, "At least one product is required")
3661
- }).refine(
3662
- (data) => new Date(data.starts_at) < new Date(data.ends_at),
3663
- "End date must be after start date"
3664
- );
3665
- const FlashSaleForm = ({
3666
- initialData,
3667
- initialProducts,
3668
- onSubmit,
3669
- onCancel,
3670
- disabled = false
3499
+ const CampaignDetailForm = ({
3500
+ campaignId,
3501
+ campaignName
3671
3502
  }) => {
3672
- var _a;
3673
- const {
3674
- control,
3675
- register,
3676
- handleSubmit,
3677
- watch,
3678
- setValue,
3679
- formState: { errors }
3680
- } = useForm({
3681
- resolver: t(campaignDataSchema),
3682
- defaultValues: {
3683
- name: (initialData == null ? void 0 : initialData.name) || "",
3684
- description: (initialData == null ? void 0 : initialData.description) || "",
3685
- type: "flash-sale",
3686
- starts_at: (initialData == null ? void 0 : initialData.starts_at) || "",
3687
- ends_at: (initialData == null ? void 0 : initialData.ends_at) || "",
3688
- products: initialProducts ? Array.from(initialProducts.values()) : []
3689
- }
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
- const [openProductModal, setOpenProductModal] = useState(false);
3696
- const startsAt = watch("starts_at");
3697
- const endsAt = watch("ends_at");
3698
- const handleDateTimeChange = (field, type, value) => {
3699
- const currentValue = watch(field);
3700
- if (type === "date") {
3701
- const time = currentValue ? dayjs(currentValue).format("HH:mm") : field === "starts_at" ? "00:00" : "23:59";
3702
- setValue(field, `${value}T${time}`);
3703
- } else {
3704
- const date = currentValue ? dayjs(currentValue).format("YYYY-MM-DD") : dayjs().format("YYYY-MM-DD");
3705
- setValue(field, `${date}T${value}`);
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 onFormSubmit = (data) => {
3709
- onSubmit(data);
3710
- };
3711
- const onFormError = () => {
3712
- const errorMessages = Object.entries(errors).map(([key, value]) => {
3713
- var _a2;
3714
- if (key === "products" && Array.isArray(value)) {
3715
- return value.map(
3716
- (item, index) => item ? `Product ${index + 1}: ${Object.values(item).join(", ")}` : ""
3717
- ).filter(Boolean).join(", ");
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
- return (value == null ? void 0 : value.message) || ((_a2 = value == null ? void 0 : value.root) == null ? void 0 : _a2.message);
3720
- }).filter(Boolean).join(", ");
3721
- toast.error("Invalid data", {
3722
- description: errorMessages
3723
- });
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
- return /* @__PURE__ */ jsxs(Container, { children: [
3726
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
3727
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "Flash sale campaign" }),
3728
- /* @__PURE__ */ jsx(Button, { variant: "transparent", onClick: onCancel, children: "Cancel" })
3729
- ] }),
3730
- /* @__PURE__ */ jsxs(
3731
- "form",
3732
- {
3733
- onSubmit: handleSubmit(onFormSubmit, onFormError),
3734
- className: "space-y-4 my-8",
3735
- children: [
3736
- /* @__PURE__ */ jsxs("div", { children: [
3737
- /* @__PURE__ */ jsx(Label, { children: "Name" }),
3738
- /* @__PURE__ */ jsx(Input, { ...register("name"), disabled }),
3739
- errors.name && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.name.message })
3740
- ] }),
3741
- /* @__PURE__ */ jsxs("div", { children: [
3742
- /* @__PURE__ */ jsx(Label, { children: "Description" }),
3743
- /* @__PURE__ */ jsx(Textarea, { ...register("description"), disabled }),
3744
- errors.description && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
3745
- ] }),
3746
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
3747
- /* @__PURE__ */ jsxs("div", { children: [
3748
- /* @__PURE__ */ jsx(Label, { children: "Start Date" }),
3749
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
3750
- /* @__PURE__ */ jsx(
3751
- Input,
3752
- {
3753
- type: "date",
3754
- value: startsAt ? dayjs(startsAt).format("YYYY-MM-DD") : "",
3755
- onChange: (e2) => handleDateTimeChange("starts_at", "date", e2.target.value),
3756
- disabled,
3757
- className: "flex-1"
3758
- }
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__ */ jsx(
3761
- Input,
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
- type: "time",
3764
- value: startsAt ? dayjs(startsAt).format("HH:mm") : "",
3765
- onChange: (e2) => handleDateTimeChange("starts_at", "time", e2.target.value),
3766
- disabled,
3767
- className: "w-32"
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
- errors.starts_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
3772
- ] }),
3773
- /* @__PURE__ */ jsxs("div", { children: [
3774
- /* @__PURE__ */ jsx(Label, { children: "End Date" }),
3775
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
3776
- /* @__PURE__ */ jsx(
3777
- Input,
3778
- {
3779
- type: "date",
3780
- value: endsAt ? dayjs(endsAt).format("YYYY-MM-DD") : "",
3781
- onChange: (e2) => handleDateTimeChange("ends_at", "date", e2.target.value),
3782
- disabled,
3783
- className: "flex-1"
3784
- }
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__ */ jsx(
3787
- Input,
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
- type: "time",
3790
- value: endsAt ? dayjs(endsAt).format("HH:mm") : "",
3791
- onChange: (e2) => handleDateTimeChange("ends_at", "time", e2.target.value),
3792
- disabled,
3793
- className: "w-32"
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
- errors.ends_at && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
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: "flex justify-between items-center", children: [
3801
- /* @__PURE__ */ jsx(Label, { children: "Products" }),
3745
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
3746
+ /* @__PURE__ */ jsx(Label, { htmlFor: "link-text", children: "Link Text" }),
3802
3747
  /* @__PURE__ */ jsx(
3803
- Button,
3748
+ Input,
3804
3749
  {
3805
- type: "button",
3806
- variant: "secondary",
3807
- onClick: () => setOpenProductModal(true),
3808
- disabled,
3809
- children: "Add Product"
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
- ((_a = errors.products) == null ? void 0 : _a.root) && /* @__PURE__ */ jsx("p", { className: "text-red-500 text-sm", children: errors.products.root.message }),
3814
- /* @__PURE__ */ jsxs(Table, { children: [
3815
- /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
3816
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Product" }),
3817
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Discount Type" }),
3818
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Discount Value" }),
3819
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Limit" }),
3820
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Max Qty per Order" }),
3821
- /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Actions" })
3822
- ] }) }),
3823
- /* @__PURE__ */ jsx(Table.Body, { children: fields.map((field, index) => /* @__PURE__ */ jsxs(Table.Row, { children: [
3824
- /* @__PURE__ */ jsx(Table.Cell, { children: field.product.title }),
3825
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
3826
- Controller,
3827
- {
3828
- name: `products.${index}.discountType`,
3829
- control,
3830
- render: ({ field: field2 }) => /* @__PURE__ */ jsxs(
3831
- Select,
3832
- {
3833
- value: field2.value,
3834
- onValueChange: field2.onChange,
3835
- disabled,
3836
- children: [
3837
- /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Select discount type" }) }),
3838
- /* @__PURE__ */ jsx(Select.Content, { children: /* @__PURE__ */ jsx(Select.Item, { value: "percentage", children: "Percentage" }) })
3839
- ]
3840
- }
3841
- )
3842
- }
3843
- ) }),
3844
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
3845
- Controller,
3846
- {
3847
- name: `products.${index}.discountValue`,
3848
- control,
3849
- render: ({ field: field2 }) => /* @__PURE__ */ jsx(
3850
- Input,
3851
- {
3852
- type: "number",
3853
- value: field2.value,
3854
- onChange: (e2) => field2.onChange(Number(e2.target.value)),
3855
- disabled
3856
- }
3857
- )
3858
- }
3859
- ) }),
3860
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
3861
- Controller,
3862
- {
3863
- name: `products.${index}.limit`,
3864
- control,
3865
- render: ({ field: field2 }) => /* @__PURE__ */ jsx(
3866
- Input,
3867
- {
3868
- type: "number",
3869
- value: field2.value,
3870
- onChange: (e2) => field2.onChange(Number(e2.target.value)),
3871
- disabled
3872
- }
3873
- )
3874
- }
3875
- ) }),
3876
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
3877
- Controller,
3878
- {
3879
- name: `products.${index}.maxQty`,
3880
- control,
3881
- render: ({ field: field2 }) => /* @__PURE__ */ jsx(
3882
- Input,
3883
- {
3884
- type: "number",
3885
- value: field2.value,
3886
- onChange: (e2) => field2.onChange(Number(e2.target.value)),
3887
- disabled
3888
- }
3889
- )
3890
- }
3891
- ) }),
3892
- /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(
3893
- Button,
3894
- {
3895
- type: "button",
3896
- variant: "danger",
3897
- onClick: () => remove(index),
3898
- disabled,
3899
- children: /* @__PURE__ */ jsx(Trash, {})
3900
- }
3901
- ) })
3902
- ] }, field.id)) })
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__ */ jsx(Button, { type: "submit", disabled, children: "Save" })
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
- /* @__PURE__ */ jsx(FocusModal, { open: openProductModal, onOpenChange: setOpenProductModal, children: /* @__PURE__ */ jsxs(FocusModal.Content, { children: [
3909
- /* @__PURE__ */ jsx(FocusModal.Header, { title: "Add Product" }),
3910
- /* @__PURE__ */ jsx(FocusModal.Body, { children: /* @__PURE__ */ jsx(
3911
- ProductSelector,
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
- selectedProductIds: fields.map((f2) => f2.product.id),
3914
- onSelectProduct: (product) => {
3915
- append({
3916
- product,
3917
- discountType: "percentage",
3918
- discountValue: 10,
3919
- limit: 10,
3920
- maxQty: 1
3921
- });
3922
- setOpenProductModal(false);
3923
- }
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: FlashSale,
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$3.label,
4120
- icon: config$3.icon,
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$2.label,
4126
- icon: config$2.icon,
4125
+ label: config$3.label,
4126
+ icon: config$3.icon,
4127
4127
  path: "/flash-sales",
4128
4128
  nested: void 0
4129
4129
  },