@lodashventure/medusa-campaign 1.4.3 → 1.4.5

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