@lodashventure/medusa-campaign 1.5.3 → 1.5.4

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.
@@ -698,1274 +698,1396 @@ const CouponForm = ({
698
698
  )
699
699
  ] });
700
700
  };
701
- const CouponCreate = () => {
702
- const navigate = reactRouterDom.useNavigate();
703
- const handleSubmit = async (formData) => {
704
- var _a, _b;
701
+ const useCouponById = (id) => {
702
+ const [data, setData] = react.useState(null);
703
+ const [isLoading, setIsLoading] = react.useState(true);
704
+ const [error, setError] = react.useState(null);
705
+ const fetchCoupon = async () => {
706
+ if (!id) {
707
+ return;
708
+ }
709
+ setIsLoading(true);
710
+ setError(null);
705
711
  try {
706
- await axios__default.default.post("/admin/coupons", {
707
- ...formData,
708
- starts_at: new Date(formData.starts_at).toUTCString(),
709
- ends_at: new Date(formData.ends_at).toUTCString(),
710
- currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
711
- });
712
- ui.toast.success("Coupon created successfully");
713
- navigate("/coupons");
714
- } catch (error) {
715
- let message = "Failed to create coupon";
716
- if (axios__default.default.isAxiosError(error)) {
717
- message = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) ?? message;
718
- } else if (error instanceof Error) {
719
- message = error.message;
720
- }
721
- ui.toast.error("Failed to create coupon", {
722
- description: message
723
- });
712
+ const response = await axios__default.default.get(`/admin/coupons/${id}`);
713
+ setData(response.data);
714
+ } catch (err) {
715
+ setError(err instanceof Error ? err : new Error("Failed to fetch coupon"));
716
+ } finally {
717
+ setIsLoading(false);
724
718
  }
725
719
  };
726
- return /* @__PURE__ */ jsxRuntime.jsx(
727
- CouponForm,
728
- {
729
- onSubmit: handleSubmit,
730
- onCancel: () => navigate("/coupons")
731
- }
732
- );
733
- };
734
- const sdk = new Medusa__default.default({
735
- baseUrl: "/",
736
- debug: false,
737
- auth: {
738
- type: "session"
739
- }
740
- });
741
- const columnHelper = ui.createDataTableColumnHelper();
742
- const columns = [
743
- columnHelper.accessor("title", {
744
- header: "Title",
745
- cell: (info) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "large", className: "py-2", children: info.getValue() })
746
- }),
747
- columnHelper.accessor("description", {
748
- header: "Description",
749
- cell: (info) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "large", className: "py-2", children: info.getValue() })
750
- })
751
- ];
752
- const ProductSelector = ({
753
- selectedProductIds,
754
- onSelectProduct
755
- }) => {
756
- const [search, setSearch] = react.useState("");
757
- const debouncedSearchHandler = lodash.debounce((value) => {
758
- setSearch(value);
759
- }, 500);
760
- const [products, setProducts] = react.useState(null);
761
- const [isLoading, setIsLoading] = react.useState(true);
762
720
  react.useEffect(() => {
763
- const fetchProducts = async () => {
764
- setIsLoading(true);
765
- try {
766
- const result = await sdk.admin.product.list({
767
- q: search,
768
- limit: 100
769
- });
770
- setProducts(result);
771
- } catch (error) {
772
- console.error("Failed to fetch products:", error);
773
- } finally {
774
- setIsLoading(false);
775
- }
776
- };
777
- fetchProducts();
778
- }, [selectedProductIds, search]);
779
- const selectableProducts = react.useMemo(() => {
780
- var _a;
781
- return (_a = products == null ? void 0 : products.products) == null ? void 0 : _a.filter(
782
- (product) => !selectedProductIds.includes(product.id)
783
- );
784
- }, [products == null ? void 0 : products.products, selectedProductIds]);
785
- const table = ui.useDataTable({
786
- data: selectableProducts || [],
787
- columns,
788
- getRowId: (product) => product.id,
789
- onRowClick: (_, row) => {
790
- onSelectProduct == null ? void 0 : onSelectProduct(row.original);
791
- }
792
- });
793
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
794
- /* @__PURE__ */ jsxRuntime.jsx(
795
- ui.Input,
796
- {
797
- className: "text-lg py-2",
798
- placeholder: "Search products...",
799
- onChange: (e) => debouncedSearchHandler(e.target.value)
800
- }
801
- ),
802
- /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { instance: table, children: /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {}) })
803
- ] });
721
+ fetchCoupon();
722
+ }, [id]);
723
+ return {
724
+ data,
725
+ isLoading,
726
+ error,
727
+ refetch: fetchCoupon
728
+ };
804
729
  };
805
- const customCampaignSchema = z__default.default.object({
806
- name: z__default.default.string().min(1, "Name is required"),
807
- description: z__default.default.string().min(1, "Description is required"),
808
- type: z__default.default.literal("flash-sale"),
809
- starts_at: z__default.default.string().min(1, "Start date is required"),
810
- ends_at: z__default.default.string().min(1, "End date is required")
811
- });
812
- const campaignProductSchema = z__default.default.object({
813
- product: z__default.default.object({
814
- id: z__default.default.string(),
815
- title: z__default.default.string()
816
- }),
817
- discountType: z__default.default.enum([
818
- "percentage"
819
- // , "fixed"
820
- ]),
821
- discountValue: z__default.default.number().min(1),
822
- limit: z__default.default.number().min(1),
823
- maxQty: z__default.default.number().min(1)
824
- });
825
- const campaignDataSchema = customCampaignSchema.extend({
826
- products: z__default.default.array(campaignProductSchema).min(1, "At least one product is required")
827
- }).refine(
828
- (data) => new Date(data.starts_at) < new Date(data.ends_at),
829
- "End date must be after start date"
830
- );
831
- const FlashSaleForm = ({
832
- initialData,
833
- initialProducts,
834
- onSubmit,
835
- onCancel,
836
- disabled = false
730
+ const MarkdownEditor = ({
731
+ label,
732
+ value,
733
+ onChange,
734
+ placeholder,
735
+ helpText,
736
+ rows = 10,
737
+ showPreview = true
837
738
  }) => {
838
- var _a;
839
- const {
840
- control,
841
- register,
842
- handleSubmit,
843
- watch,
844
- setValue,
845
- formState: { errors }
846
- } = reactHookForm.useForm({
847
- resolver: zod.zodResolver(campaignDataSchema),
848
- defaultValues: {
849
- name: (initialData == null ? void 0 : initialData.name) || "",
850
- description: (initialData == null ? void 0 : initialData.description) || "",
851
- type: "flash-sale",
852
- starts_at: (initialData == null ? void 0 : initialData.starts_at) || "",
853
- ends_at: (initialData == null ? void 0 : initialData.ends_at) || "",
854
- products: initialProducts ? Array.from(initialProducts.values()) : []
855
- }
856
- });
857
- const { fields, append, remove, update } = reactHookForm.useFieldArray({
858
- control,
859
- name: "products"
860
- });
861
- const [openProductModal, setOpenProductModal] = react.useState(false);
862
- const startsAt = watch("starts_at");
863
- const endsAt = watch("ends_at");
864
- const handleDateTimeChange = (field, type, value) => {
865
- const currentValue = watch(field);
866
- if (type === "date") {
867
- const time = currentValue ? dayjs__default.default(currentValue).format("HH:mm") : field === "starts_at" ? "00:00" : "23:59";
868
- setValue(field, `${value}T${time}`);
869
- } else {
870
- const date = currentValue ? dayjs__default.default(currentValue).format("YYYY-MM-DD") : dayjs__default.default().format("YYYY-MM-DD");
871
- setValue(field, `${date}T${value}`);
872
- }
739
+ const [activeTab, setActiveTab] = react.useState("write");
740
+ const insertMarkdown = (before, after = "") => {
741
+ const textarea = document.getElementById(
742
+ `markdown-${label}`
743
+ );
744
+ if (!textarea) return;
745
+ const start = textarea.selectionStart;
746
+ const end = textarea.selectionEnd;
747
+ const selectedText = value.substring(start, end);
748
+ const newText = value.substring(0, start) + before + selectedText + after + value.substring(end);
749
+ onChange(newText);
750
+ setTimeout(() => {
751
+ textarea.focus();
752
+ textarea.setSelectionRange(
753
+ start + before.length,
754
+ start + before.length + selectedText.length
755
+ );
756
+ }, 0);
873
757
  };
874
- const onFormSubmit = (data) => {
875
- onSubmit(data);
758
+ const renderMarkdownPreview = (markdown) => {
759
+ let html = markdown;
760
+ html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
761
+ html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
762
+ html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
763
+ html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
764
+ html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
765
+ html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
766
+ html = html.replace(/_(.+?)_/g, "<em>$1</em>");
767
+ html = html.replace(
768
+ /```(\w+)?\n([\s\S]+?)```/g,
769
+ '<pre class="bg-ui-bg-subtle p-4 rounded-lg overflow-x-auto"><code class="language-$1">$2</code></pre>'
770
+ );
771
+ html = html.replace(
772
+ /`([^`]+)`/g,
773
+ '<code class="bg-ui-bg-subtle px-1 py-0.5 rounded text-sm">$1</code>'
774
+ );
775
+ html = html.replace(
776
+ /\[([^\]]+)\]\(([^)]+)\)/g,
777
+ '<a href="$2" class="text-ui-fg-interactive underline">$1</a>'
778
+ );
779
+ html = html.replace(
780
+ /!\[([^\]]*)\]\(([^)]+)\)/g,
781
+ '<img src="$2" alt="$1" class="max-w-full h-auto rounded-lg my-2" />'
782
+ );
783
+ html = html.replace(/^\* (.+)$/gim, "<li>$1</li>");
784
+ html = html.replace(/^\- (.+)$/gim, "<li>$1</li>");
785
+ html = html.replace(
786
+ /(<li>.*<\/li>)/s,
787
+ "<ul class='list-disc ml-6'>$1</ul>"
788
+ );
789
+ html = html.replace(/\n\n/g, "</p><p>");
790
+ html = `<p>${html}</p>`;
791
+ return html;
876
792
  };
877
- const onFormError = () => {
878
- const errorMessages = Object.entries(errors).map(([key, value]) => {
879
- var _a2;
880
- if (key === "products" && Array.isArray(value)) {
881
- return value.map(
882
- (item, index) => item ? `Product ${index + 1}: ${Object.values(item).join(", ")}` : ""
883
- ).filter(Boolean).join(", ");
884
- }
885
- return (value == null ? void 0 : value.message) || ((_a2 = value == null ? void 0 : value.root) == null ? void 0 : _a2.message);
886
- }).filter(Boolean).join(", ");
887
- ui.toast.error("Invalid data", {
888
- description: errorMessages
889
- });
890
- };
891
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
793
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
892
794
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
893
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-semibold", children: "Flash sale campaign" }),
894
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "transparent", onClick: onCancel, children: "Cancel" })
795
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: `markdown-${label}`, className: "font-medium", children: label }),
796
+ helpText && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-xs text-ui-fg-subtle", children: [
797
+ /* @__PURE__ */ jsxRuntime.jsx(icons.InformationCircle, { className: "h-4 w-4" }),
798
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: helpText })
799
+ ] })
895
800
  ] }),
896
- /* @__PURE__ */ jsxRuntime.jsxs(
897
- "form",
898
- {
899
- onSubmit: handleSubmit(onFormSubmit, onFormError),
900
- className: "space-y-4 my-8",
901
- children: [
902
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
903
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Name" }),
904
- /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...register("name"), disabled }),
905
- errors.name && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.name.message })
906
- ] }),
907
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
908
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Description" }),
909
- /* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...register("description"), disabled }),
910
- errors.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
911
- ] }),
912
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
913
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
914
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Start Date" }),
915
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
916
- /* @__PURE__ */ jsxRuntime.jsx(
917
- ui.Input,
918
- {
919
- type: "date",
920
- value: startsAt ? dayjs__default.default(startsAt).format("YYYY-MM-DD") : "",
921
- onChange: (e) => handleDateTimeChange("starts_at", "date", e.target.value),
922
- disabled,
923
- className: "flex-1"
924
- }
925
- ),
926
- /* @__PURE__ */ jsxRuntime.jsx(
927
- ui.Input,
928
- {
929
- type: "time",
930
- value: startsAt ? dayjs__default.default(startsAt).format("HH:mm") : "",
931
- onChange: (e) => handleDateTimeChange("starts_at", "time", e.target.value),
932
- disabled,
933
- className: "w-32"
934
- }
935
- )
936
- ] }),
937
- errors.starts_at && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
938
- ] }),
939
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
940
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "End Date" }),
941
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
942
- /* @__PURE__ */ jsxRuntime.jsx(
943
- ui.Input,
944
- {
945
- type: "date",
946
- value: endsAt ? dayjs__default.default(endsAt).format("YYYY-MM-DD") : "",
947
- onChange: (e) => handleDateTimeChange("ends_at", "date", e.target.value),
948
- disabled,
949
- className: "flex-1"
950
- }
951
- ),
952
- /* @__PURE__ */ jsxRuntime.jsx(
953
- ui.Input,
954
- {
955
- type: "time",
956
- value: endsAt ? dayjs__default.default(endsAt).format("HH:mm") : "",
957
- onChange: (e) => handleDateTimeChange("ends_at", "time", e.target.value),
958
- disabled,
959
- className: "w-32"
960
- }
961
- )
962
- ] }),
963
- errors.ends_at && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
964
- ] })
965
- ] }),
966
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
967
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Products" }),
968
- /* @__PURE__ */ jsxRuntime.jsx(
969
- ui.Button,
970
- {
971
- type: "button",
972
- variant: "secondary",
973
- onClick: () => setOpenProductModal(true),
974
- disabled,
975
- children: "Add Product"
976
- }
977
- )
978
- ] }),
979
- ((_a = errors.products) == null ? void 0 : _a.root) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm", children: errors.products.root.message }),
980
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
981
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
982
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
983
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Discount Type" }),
984
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Discount Value" }),
985
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Limit" }),
986
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Max Qty per Order" }),
987
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Actions" })
988
- ] }) }),
989
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: fields.map((field, index) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
990
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: field.product.title }),
991
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
992
- reactHookForm.Controller,
993
- {
994
- name: `products.${index}.discountType`,
995
- control,
996
- render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsxs(
997
- ui.Select,
998
- {
999
- value: field2.value,
1000
- onValueChange: field2.onChange,
1001
- disabled,
1002
- children: [
1003
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select discount type" }) }),
1004
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "percentage", children: "Percentage" }) })
1005
- ]
1006
- }
1007
- )
1008
- }
1009
- ) }),
1010
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
1011
- reactHookForm.Controller,
1012
- {
1013
- name: `products.${index}.discountValue`,
1014
- control,
1015
- render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsx(
1016
- ui.Input,
1017
- {
1018
- type: "number",
1019
- value: field2.value,
1020
- onChange: (e) => field2.onChange(Number(e.target.value)),
1021
- disabled
1022
- }
1023
- )
1024
- }
1025
- ) }),
1026
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
1027
- reactHookForm.Controller,
1028
- {
1029
- name: `products.${index}.limit`,
1030
- control,
1031
- render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsx(
1032
- ui.Input,
1033
- {
1034
- type: "number",
1035
- value: field2.value,
1036
- onChange: (e) => field2.onChange(Number(e.target.value)),
1037
- disabled
1038
- }
1039
- )
1040
- }
1041
- ) }),
1042
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
1043
- reactHookForm.Controller,
1044
- {
1045
- name: `products.${index}.maxQty`,
1046
- control,
1047
- render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsx(
1048
- ui.Input,
1049
- {
1050
- type: "number",
1051
- value: field2.value,
1052
- onChange: (e) => field2.onChange(Number(e.target.value)),
1053
- disabled
1054
- }
1055
- )
1056
- }
1057
- ) }),
1058
- /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
1059
- ui.Button,
1060
- {
1061
- type: "button",
1062
- variant: "danger",
1063
- onClick: () => remove(index),
1064
- disabled,
1065
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1066
- }
1067
- ) })
1068
- ] }, field.id)) })
1069
- ] }),
1070
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled, children: "Save" })
1071
- ]
1072
- }
1073
- ),
1074
- /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal, { open: openProductModal, onOpenChange: setOpenProductModal, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Content, { children: [
1075
- /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Header, { title: "Add Product" }),
1076
- /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Body, { children: /* @__PURE__ */ jsxRuntime.jsx(
1077
- ProductSelector,
801
+ showPreview ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { value: activeTab, onValueChange: (v) => setActiveTab(v), children: [
802
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
803
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "write", children: "Write" }),
804
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "preview", children: "Preview" })
805
+ ] }),
806
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "write", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
807
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
808
+ /* @__PURE__ */ jsxRuntime.jsx(
809
+ ui.Button,
810
+ {
811
+ type: "button",
812
+ variant: "secondary",
813
+ size: "small",
814
+ onClick: () => insertMarkdown("**", "**"),
815
+ title: "Bold",
816
+ children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "B" })
817
+ }
818
+ ),
819
+ /* @__PURE__ */ jsxRuntime.jsx(
820
+ ui.Button,
821
+ {
822
+ type: "button",
823
+ variant: "secondary",
824
+ size: "small",
825
+ onClick: () => insertMarkdown("*", "*"),
826
+ title: "Italic",
827
+ children: /* @__PURE__ */ jsxRuntime.jsx("em", { children: "I" })
828
+ }
829
+ ),
830
+ /* @__PURE__ */ jsxRuntime.jsx(
831
+ ui.Button,
832
+ {
833
+ type: "button",
834
+ variant: "secondary",
835
+ size: "small",
836
+ onClick: () => insertMarkdown("`", "`"),
837
+ title: "Inline Code",
838
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.CommandLine, { className: "h-4 w-4" })
839
+ }
840
+ ),
841
+ /* @__PURE__ */ jsxRuntime.jsx(
842
+ ui.Button,
843
+ {
844
+ type: "button",
845
+ variant: "secondary",
846
+ size: "small",
847
+ onClick: () => insertMarkdown("## "),
848
+ title: "Heading",
849
+ children: "H2"
850
+ }
851
+ ),
852
+ /* @__PURE__ */ jsxRuntime.jsx(
853
+ ui.Button,
854
+ {
855
+ type: "button",
856
+ variant: "secondary",
857
+ size: "small",
858
+ onClick: () => insertMarkdown("### "),
859
+ title: "Heading",
860
+ children: "H3"
861
+ }
862
+ ),
863
+ /* @__PURE__ */ jsxRuntime.jsx(
864
+ ui.Button,
865
+ {
866
+ type: "button",
867
+ variant: "secondary",
868
+ size: "small",
869
+ onClick: () => insertMarkdown("[", "](url)"),
870
+ title: "Link",
871
+ children: "Link"
872
+ }
873
+ ),
874
+ /* @__PURE__ */ jsxRuntime.jsx(
875
+ ui.Button,
876
+ {
877
+ type: "button",
878
+ variant: "secondary",
879
+ size: "small",
880
+ onClick: () => insertMarkdown("![alt](", ")"),
881
+ title: "Image",
882
+ children: "Image"
883
+ }
884
+ ),
885
+ /* @__PURE__ */ jsxRuntime.jsx(
886
+ ui.Button,
887
+ {
888
+ type: "button",
889
+ variant: "secondary",
890
+ size: "small",
891
+ onClick: () => insertMarkdown("```\n", "\n```"),
892
+ title: "Code Block",
893
+ children: "Code"
894
+ }
895
+ ),
896
+ /* @__PURE__ */ jsxRuntime.jsx(
897
+ ui.Button,
898
+ {
899
+ type: "button",
900
+ variant: "secondary",
901
+ size: "small",
902
+ onClick: () => insertMarkdown("- "),
903
+ title: "List",
904
+ children: "List"
905
+ }
906
+ )
907
+ ] }),
908
+ /* @__PURE__ */ jsxRuntime.jsx(
909
+ ui.Textarea,
910
+ {
911
+ id: `markdown-${label}`,
912
+ value,
913
+ onChange: (e) => onChange(e.target.value),
914
+ placeholder,
915
+ rows,
916
+ className: "font-mono text-sm"
917
+ }
918
+ )
919
+ ] }) }),
920
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "preview", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
921
+ "div",
1078
922
  {
1079
- selectedProductIds: fields.map((f) => f.product.id),
1080
- onSelectProduct: (product) => {
1081
- append({
1082
- product,
1083
- discountType: "percentage",
1084
- discountValue: 10,
1085
- limit: 10,
1086
- maxQty: 1
1087
- });
1088
- setOpenProductModal(false);
923
+ className: ui.clx(
924
+ "min-h-[200px] rounded-lg border border-ui-border-base bg-ui-bg-subtle p-4",
925
+ "prose prose-sm max-w-none"
926
+ ),
927
+ dangerouslySetInnerHTML: {
928
+ __html: value ? renderMarkdownPreview(value) : '<p class="text-ui-fg-subtle">Nothing to preview</p>'
1089
929
  }
1090
930
  }
1091
931
  ) })
1092
- ] }) })
932
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
933
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
934
+ /* @__PURE__ */ jsxRuntime.jsx(
935
+ ui.Button,
936
+ {
937
+ type: "button",
938
+ variant: "secondary",
939
+ size: "small",
940
+ onClick: () => insertMarkdown("**", "**"),
941
+ title: "Bold",
942
+ children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "B" })
943
+ }
944
+ ),
945
+ /* @__PURE__ */ jsxRuntime.jsx(
946
+ ui.Button,
947
+ {
948
+ type: "button",
949
+ variant: "secondary",
950
+ size: "small",
951
+ onClick: () => insertMarkdown("*", "*"),
952
+ title: "Italic",
953
+ children: /* @__PURE__ */ jsxRuntime.jsx("em", { children: "I" })
954
+ }
955
+ ),
956
+ /* @__PURE__ */ jsxRuntime.jsx(
957
+ ui.Button,
958
+ {
959
+ type: "button",
960
+ variant: "secondary",
961
+ size: "small",
962
+ onClick: () => insertMarkdown("`", "`"),
963
+ title: "Inline Code",
964
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.CommandLine, { className: "h-4 w-4" })
965
+ }
966
+ ),
967
+ /* @__PURE__ */ jsxRuntime.jsx(
968
+ ui.Button,
969
+ {
970
+ type: "button",
971
+ variant: "secondary",
972
+ size: "small",
973
+ onClick: () => insertMarkdown("```\n", "\n```"),
974
+ title: "Code Block",
975
+ children: "Code"
976
+ }
977
+ )
978
+ ] }),
979
+ /* @__PURE__ */ jsxRuntime.jsx(
980
+ ui.Textarea,
981
+ {
982
+ id: `markdown-${label}`,
983
+ value,
984
+ onChange: (e) => onChange(e.target.value),
985
+ placeholder,
986
+ rows,
987
+ className: "font-mono text-sm"
988
+ }
989
+ )
990
+ ] }),
991
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-ui-fg-subtle", children: "Supports Markdown formatting: **bold**, *italic*, `code`, ```code blocks```, [links](url), ![images](url), and lists" })
1093
992
  ] });
1094
993
  };
1095
- const useFlashSaleById = (id) => {
1096
- const [data, setData] = react.useState(null);
1097
- const [isLoading, setIsLoading] = react.useState(true);
994
+ const CampaignImageUploader = ({
995
+ campaignId,
996
+ imageType,
997
+ currentImageUrl,
998
+ onClose,
999
+ onSuccess
1000
+ }) => {
1001
+ const [displayImage, setDisplayImage] = react.useState(currentImageUrl || null);
1002
+ const [isUploading, setIsUploading] = react.useState(false);
1003
+ const [isDragging, setIsDragging] = react.useState(false);
1098
1004
  const [error, setError] = react.useState(null);
1099
- const fetchFlashSale = async () => {
1100
- if (!id) {
1101
- setIsLoading(false);
1102
- return;
1005
+ const [previewUrl, setPreviewUrl] = react.useState(null);
1006
+ const isThumbnail = imageType === "thumbnail";
1007
+ const maxSize = 5;
1008
+ const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
1009
+ const formatFileTypes = () => {
1010
+ return "JPEG, PNG, GIF, or WebP";
1011
+ };
1012
+ const validateFile = (file) => {
1013
+ if (!allowedTypes.includes(file.type)) {
1014
+ setError(`Please upload a valid image file (${formatFileTypes()})`);
1015
+ return false;
1103
1016
  }
1104
- setIsLoading(true);
1017
+ if (file.size > maxSize * 1024 * 1024) {
1018
+ setError(`File size must be less than ${maxSize}MB`);
1019
+ return false;
1020
+ }
1021
+ return true;
1022
+ };
1023
+ const handleFileUpload = async (file) => {
1024
+ var _a, _b;
1025
+ if (!validateFile(file)) return;
1105
1026
  setError(null);
1027
+ const reader = new FileReader();
1028
+ reader.onloadend = () => {
1029
+ setPreviewUrl(reader.result);
1030
+ };
1031
+ reader.readAsDataURL(file);
1032
+ setIsUploading(true);
1033
+ const formData = new FormData();
1034
+ formData.append("file", file);
1106
1035
  try {
1107
- const response = await axios__default.default.get(`/admin/flash-sales/${id}`);
1108
- setData(response.data);
1109
- } catch (err) {
1110
- setError(
1111
- err instanceof Error ? err : new Error("Failed to fetch flash sale")
1036
+ const response = await fetch(
1037
+ `/admin/campaigns/${campaignId}/${imageType}`,
1038
+ {
1039
+ method: "POST",
1040
+ body: formData,
1041
+ credentials: "include"
1042
+ }
1112
1043
  );
1044
+ if (response.ok) {
1045
+ const result = await response.json();
1046
+ const newImageUrl = imageType === "image" ? (_a = result.campaign_detail) == null ? void 0 : _a.image_url : (_b = result.campaign_detail) == null ? void 0 : _b.thumbnail_url;
1047
+ setDisplayImage(newImageUrl);
1048
+ setPreviewUrl(null);
1049
+ onSuccess == null ? void 0 : onSuccess();
1050
+ setTimeout(() => {
1051
+ onClose();
1052
+ }, 1e3);
1053
+ } else {
1054
+ const errorData = await response.json();
1055
+ setError(errorData.error || `Failed to upload ${imageType}`);
1056
+ setPreviewUrl(null);
1057
+ }
1058
+ } catch (err) {
1059
+ setError(`Error uploading ${imageType}`);
1060
+ setPreviewUrl(null);
1061
+ console.error(`Error uploading ${imageType}:`, err);
1113
1062
  } finally {
1114
- setIsLoading(false);
1063
+ setIsUploading(false);
1115
1064
  }
1116
1065
  };
1117
- react.useEffect(() => {
1118
- fetchFlashSale();
1119
- }, [id]);
1120
- return {
1121
- data,
1122
- isLoading,
1123
- error,
1124
- refetch: fetchFlashSale
1125
- };
1126
- };
1127
- const MarkdownEditor = ({
1128
- label,
1129
- value,
1130
- onChange,
1131
- placeholder,
1132
- helpText,
1133
- rows = 10,
1134
- showPreview = true
1135
- }) => {
1136
- const [activeTab, setActiveTab] = react.useState("write");
1137
- const insertMarkdown = (before, after = "") => {
1138
- const textarea = document.getElementById(
1139
- `markdown-${label}`
1140
- );
1141
- if (!textarea) return;
1142
- const start = textarea.selectionStart;
1143
- const end = textarea.selectionEnd;
1144
- const selectedText = value.substring(start, end);
1145
- const newText = value.substring(0, start) + before + selectedText + after + value.substring(end);
1146
- onChange(newText);
1147
- setTimeout(() => {
1148
- textarea.focus();
1149
- textarea.setSelectionRange(
1150
- start + before.length,
1151
- start + before.length + selectedText.length
1066
+ const handleDelete = async () => {
1067
+ if (!confirm(`Are you sure you want to delete the ${imageType}?`)) return;
1068
+ setIsUploading(true);
1069
+ setError(null);
1070
+ try {
1071
+ const response = await fetch(
1072
+ `/admin/campaigns/${campaignId}/${imageType}`,
1073
+ {
1074
+ method: "DELETE",
1075
+ credentials: "include"
1076
+ }
1152
1077
  );
1153
- }, 0);
1154
- };
1155
- const renderMarkdownPreview = (markdown) => {
1156
- let html = markdown;
1157
- html = html.replace(/^### (.*$)/gim, "<h3>$1</h3>");
1158
- html = html.replace(/^## (.*$)/gim, "<h2>$1</h2>");
1159
- html = html.replace(/^# (.*$)/gim, "<h1>$1</h1>");
1160
- html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
1161
- html = html.replace(/__(.+?)__/g, "<strong>$1</strong>");
1162
- html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
1163
- html = html.replace(/_(.+?)_/g, "<em>$1</em>");
1164
- html = html.replace(
1165
- /```(\w+)?\n([\s\S]+?)```/g,
1166
- '<pre class="bg-ui-bg-subtle p-4 rounded-lg overflow-x-auto"><code class="language-$1">$2</code></pre>'
1167
- );
1168
- html = html.replace(
1169
- /`([^`]+)`/g,
1170
- '<code class="bg-ui-bg-subtle px-1 py-0.5 rounded text-sm">$1</code>'
1171
- );
1172
- html = html.replace(
1173
- /\[([^\]]+)\]\(([^)]+)\)/g,
1174
- '<a href="$2" class="text-ui-fg-interactive underline">$1</a>'
1175
- );
1176
- html = html.replace(
1177
- /!\[([^\]]*)\]\(([^)]+)\)/g,
1178
- '<img src="$2" alt="$1" class="max-w-full h-auto rounded-lg my-2" />'
1179
- );
1180
- html = html.replace(/^\* (.+)$/gim, "<li>$1</li>");
1181
- html = html.replace(/^\- (.+)$/gim, "<li>$1</li>");
1182
- html = html.replace(
1183
- /(<li>.*<\/li>)/s,
1184
- "<ul class='list-disc ml-6'>$1</ul>"
1185
- );
1186
- html = html.replace(/\n\n/g, "</p><p>");
1187
- html = `<p>${html}</p>`;
1188
- return html;
1078
+ if (response.ok) {
1079
+ setDisplayImage(null);
1080
+ setPreviewUrl(null);
1081
+ onSuccess == null ? void 0 : onSuccess();
1082
+ setTimeout(() => {
1083
+ onClose();
1084
+ }, 500);
1085
+ } else {
1086
+ const errorData = await response.json();
1087
+ setError(errorData.error || `Failed to delete ${imageType}`);
1088
+ }
1089
+ } catch (err) {
1090
+ setError(`Error deleting ${imageType}`);
1091
+ console.error(`Error deleting ${imageType}:`, err);
1092
+ } finally {
1093
+ setIsUploading(false);
1094
+ }
1189
1095
  };
1190
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1191
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1192
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: `markdown-${label}`, className: "font-medium", children: label }),
1193
- helpText && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-xs text-ui-fg-subtle", children: [
1194
- /* @__PURE__ */ jsxRuntime.jsx(icons.InformationCircle, { className: "h-4 w-4" }),
1195
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: helpText })
1196
- ] })
1197
- ] }),
1198
- showPreview ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { value: activeTab, onValueChange: (v) => setActiveTab(v), children: [
1199
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
1200
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "write", children: "Write" }),
1201
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "preview", children: "Preview" })
1096
+ const handleDragEnter = react.useCallback((e) => {
1097
+ e.preventDefault();
1098
+ e.stopPropagation();
1099
+ setIsDragging(true);
1100
+ }, []);
1101
+ const handleDragLeave = react.useCallback((e) => {
1102
+ e.preventDefault();
1103
+ e.stopPropagation();
1104
+ setIsDragging(false);
1105
+ }, []);
1106
+ const handleDragOver = react.useCallback((e) => {
1107
+ e.preventDefault();
1108
+ e.stopPropagation();
1109
+ }, []);
1110
+ const handleDrop = react.useCallback((e) => {
1111
+ var _a;
1112
+ e.preventDefault();
1113
+ e.stopPropagation();
1114
+ setIsDragging(false);
1115
+ const file = (_a = e.dataTransfer.files) == null ? void 0 : _a[0];
1116
+ if (file) {
1117
+ handleFileUpload(file);
1118
+ }
1119
+ }, []);
1120
+ const imageToDisplay = previewUrl || displayImage;
1121
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-2xl rounded-lg bg-ui-bg-base p-6", children: [
1122
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
1123
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1124
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Heading, { level: "h2", children: [
1125
+ "Upload Campaign ",
1126
+ isThumbnail ? "Thumbnail" : "Image"
1127
+ ] }),
1128
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: isThumbnail ? "Thumbnail for campaign listing" : "Main campaign image" })
1202
1129
  ] }),
1203
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "write", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1204
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
1205
- /* @__PURE__ */ jsxRuntime.jsx(
1206
- ui.Button,
1207
- {
1208
- type: "button",
1209
- variant: "secondary",
1210
- size: "small",
1211
- onClick: () => insertMarkdown("**", "**"),
1212
- title: "Bold",
1213
- children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "B" })
1214
- }
1215
- ),
1216
- /* @__PURE__ */ jsxRuntime.jsx(
1217
- ui.Button,
1218
- {
1219
- type: "button",
1220
- variant: "secondary",
1221
- size: "small",
1222
- onClick: () => insertMarkdown("*", "*"),
1223
- title: "Italic",
1224
- children: /* @__PURE__ */ jsxRuntime.jsx("em", { children: "I" })
1225
- }
1226
- ),
1227
- /* @__PURE__ */ jsxRuntime.jsx(
1228
- ui.Button,
1229
- {
1230
- type: "button",
1231
- variant: "secondary",
1232
- size: "small",
1233
- onClick: () => insertMarkdown("`", "`"),
1234
- title: "Inline Code",
1235
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.CommandLine, { className: "h-4 w-4" })
1236
- }
1237
- ),
1238
- /* @__PURE__ */ jsxRuntime.jsx(
1239
- ui.Button,
1240
- {
1241
- type: "button",
1242
- variant: "secondary",
1243
- size: "small",
1244
- onClick: () => insertMarkdown("## "),
1245
- title: "Heading",
1246
- children: "H2"
1247
- }
1248
- ),
1249
- /* @__PURE__ */ jsxRuntime.jsx(
1250
- ui.Button,
1251
- {
1252
- type: "button",
1253
- variant: "secondary",
1254
- size: "small",
1255
- onClick: () => insertMarkdown("### "),
1256
- title: "Heading",
1257
- children: "H3"
1258
- }
1259
- ),
1260
- /* @__PURE__ */ jsxRuntime.jsx(
1261
- ui.Button,
1262
- {
1263
- type: "button",
1264
- variant: "secondary",
1265
- size: "small",
1266
- onClick: () => insertMarkdown("[", "](url)"),
1267
- title: "Link",
1268
- children: "Link"
1269
- }
1270
- ),
1130
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(icons.X, {}) })
1131
+ ] }),
1132
+ error && /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "error", dismissible: true, className: "mb-4", children: error }),
1133
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1134
+ imageToDisplay ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1135
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-subtle", children: [
1271
1136
  /* @__PURE__ */ jsxRuntime.jsx(
1272
- ui.Button,
1137
+ "img",
1273
1138
  {
1274
- type: "button",
1275
- variant: "secondary",
1276
- size: "small",
1277
- onClick: () => insertMarkdown("![alt](", ")"),
1278
- title: "Image",
1279
- children: "Image"
1139
+ src: imageToDisplay,
1140
+ alt: `Campaign ${imageType}`,
1141
+ className: ui.clx(
1142
+ "w-full object-contain",
1143
+ isThumbnail ? "h-32" : "h-64"
1144
+ )
1280
1145
  }
1281
1146
  ),
1282
- /* @__PURE__ */ jsxRuntime.jsx(
1147
+ isUploading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-white", children: "Uploading..." }) })
1148
+ ] }),
1149
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1150
+ /* @__PURE__ */ jsxRuntime.jsxs(
1283
1151
  ui.Button,
1284
1152
  {
1285
- type: "button",
1286
1153
  variant: "secondary",
1287
- size: "small",
1288
- onClick: () => insertMarkdown("```\n", "\n```"),
1289
- title: "Code Block",
1290
- children: "Code"
1154
+ disabled: isUploading,
1155
+ onClick: () => {
1156
+ var _a;
1157
+ return (_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click();
1158
+ },
1159
+ children: [
1160
+ /* @__PURE__ */ jsxRuntime.jsx(icons.CloudArrowUp, { className: "mr-2" }),
1161
+ "Replace"
1162
+ ]
1291
1163
  }
1292
1164
  ),
1293
- /* @__PURE__ */ jsxRuntime.jsx(
1165
+ /* @__PURE__ */ jsxRuntime.jsxs(
1294
1166
  ui.Button,
1295
1167
  {
1296
- type: "button",
1297
- variant: "secondary",
1298
- size: "small",
1299
- onClick: () => insertMarkdown("- "),
1300
- title: "List",
1301
- children: "List"
1168
+ variant: "danger",
1169
+ disabled: isUploading,
1170
+ onClick: handleDelete,
1171
+ children: [
1172
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "mr-2" }),
1173
+ "Delete"
1174
+ ]
1302
1175
  }
1303
1176
  )
1304
- ] }),
1305
- /* @__PURE__ */ jsxRuntime.jsx(
1306
- ui.Textarea,
1307
- {
1308
- id: `markdown-${label}`,
1309
- value,
1310
- onChange: (e) => onChange(e.target.value),
1311
- placeholder,
1312
- rows,
1313
- className: "font-mono text-sm"
1314
- }
1315
- )
1316
- ] }) }),
1317
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "preview", className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
1177
+ ] })
1178
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
1318
1179
  "div",
1319
1180
  {
1320
1181
  className: ui.clx(
1321
- "min-h-[200px] rounded-lg border border-ui-border-base bg-ui-bg-subtle p-4",
1322
- "prose prose-sm max-w-none"
1182
+ "flex flex-col items-center justify-center rounded-lg border-2 border-dashed transition-colors",
1183
+ isThumbnail ? "h-48" : "h-64",
1184
+ isDragging ? "border-ui-border-interactive bg-ui-bg-highlight" : "border-ui-border-base bg-ui-bg-subtle",
1185
+ !isUploading && "cursor-pointer"
1323
1186
  ),
1324
- dangerouslySetInnerHTML: {
1325
- __html: value ? renderMarkdownPreview(value) : '<p class="text-ui-fg-subtle">Nothing to preview</p>'
1326
- }
1187
+ onDragEnter: handleDragEnter,
1188
+ onDragLeave: handleDragLeave,
1189
+ onDragOver: handleDragOver,
1190
+ onDrop: handleDrop,
1191
+ onClick: () => {
1192
+ var _a;
1193
+ return !isUploading && ((_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click());
1194
+ },
1195
+ children: [
1196
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }),
1197
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-2 text-lg font-medium", children: isDragging ? `Drop ${imageType} here` : `Upload ${isThumbnail ? "Thumbnail" : "Image"}` }),
1198
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-4 text-center text-ui-fg-subtle", children: "Drag and drop an image here, or click to select" }),
1199
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-sm text-ui-fg-subtle", children: [
1200
+ formatFileTypes(),
1201
+ " • Max ",
1202
+ maxSize,
1203
+ "MB"
1204
+ ] }),
1205
+ isThumbnail && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: 16:9 aspect ratio, minimum 400x225px" }),
1206
+ !isThumbnail && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: High resolution, minimum 1200x600px" }),
1207
+ isUploading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Uploading..." }) })
1208
+ ]
1209
+ }
1210
+ ),
1211
+ /* @__PURE__ */ jsxRuntime.jsx(
1212
+ "input",
1213
+ {
1214
+ id: `${imageType}-file-input`,
1215
+ type: "file",
1216
+ accept: allowedTypes.join(","),
1217
+ onChange: (e) => {
1218
+ var _a;
1219
+ const file = (_a = e.target.files) == null ? void 0 : _a[0];
1220
+ if (file) handleFileUpload(file);
1221
+ e.target.value = "";
1222
+ },
1223
+ className: "hidden"
1224
+ }
1225
+ )
1226
+ ] }),
1227
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 flex items-center justify-between border-t pt-4", children: [
1228
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: isThumbnail ? "Thumbnail will be displayed in campaign listings" : "Main image for the campaign detail page" }),
1229
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: onClose, children: "Close" })
1230
+ ] })
1231
+ ] }) });
1232
+ };
1233
+ const CampaignDetailForm = ({
1234
+ campaignId,
1235
+ campaignName
1236
+ }) => {
1237
+ const [campaignDetail, setCampaignDetail] = react.useState(
1238
+ null
1239
+ );
1240
+ const [isLoading, setIsLoading] = react.useState(true);
1241
+ const [isSaving, setIsSaving] = react.useState(false);
1242
+ const [showImageUploader, setShowImageUploader] = react.useState(null);
1243
+ const [formData, setFormData] = react.useState({
1244
+ detail_content: "",
1245
+ terms_and_conditions: "",
1246
+ meta_title: "",
1247
+ meta_description: "",
1248
+ meta_keywords: "",
1249
+ link_url: "",
1250
+ link_text: "",
1251
+ display_order: 0
1252
+ });
1253
+ react.useEffect(() => {
1254
+ fetchCampaignDetail();
1255
+ }, [campaignId]);
1256
+ const fetchCampaignDetail = async () => {
1257
+ setIsLoading(true);
1258
+ try {
1259
+ const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
1260
+ credentials: "include"
1261
+ });
1262
+ if (response.ok) {
1263
+ const data = await response.json();
1264
+ if (data.campaign_detail) {
1265
+ console.log("Campaign detail loaded:", {
1266
+ image_url: data.campaign_detail.image_url,
1267
+ thumbnail_url: data.campaign_detail.thumbnail_url,
1268
+ decoded_image: data.campaign_detail.image_url ? decodeURIComponent(data.campaign_detail.image_url) : null,
1269
+ decoded_thumbnail: data.campaign_detail.thumbnail_url ? decodeURIComponent(data.campaign_detail.thumbnail_url) : null
1270
+ });
1271
+ setCampaignDetail(data.campaign_detail);
1272
+ setFormData({
1273
+ detail_content: data.campaign_detail.detail_content || "",
1274
+ terms_and_conditions: data.campaign_detail.terms_and_conditions || "",
1275
+ meta_title: data.campaign_detail.meta_title || "",
1276
+ meta_description: data.campaign_detail.meta_description || "",
1277
+ meta_keywords: data.campaign_detail.meta_keywords || "",
1278
+ link_url: data.campaign_detail.link_url || "",
1279
+ link_text: data.campaign_detail.link_text || "",
1280
+ display_order: data.campaign_detail.display_order || 0
1281
+ });
1327
1282
  }
1328
- ) })
1329
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1330
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2 border-b pb-2", children: [
1331
- /* @__PURE__ */ jsxRuntime.jsx(
1332
- ui.Button,
1333
- {
1334
- type: "button",
1335
- variant: "secondary",
1336
- size: "small",
1337
- onClick: () => insertMarkdown("**", "**"),
1338
- title: "Bold",
1339
- children: /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "B" })
1340
- }
1341
- ),
1342
- /* @__PURE__ */ jsxRuntime.jsx(
1343
- ui.Button,
1344
- {
1345
- type: "button",
1346
- variant: "secondary",
1347
- size: "small",
1348
- onClick: () => insertMarkdown("*", "*"),
1349
- title: "Italic",
1350
- children: /* @__PURE__ */ jsxRuntime.jsx("em", { children: "I" })
1351
- }
1352
- ),
1353
- /* @__PURE__ */ jsxRuntime.jsx(
1354
- ui.Button,
1355
- {
1356
- type: "button",
1357
- variant: "secondary",
1358
- size: "small",
1359
- onClick: () => insertMarkdown("`", "`"),
1360
- title: "Inline Code",
1361
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.CommandLine, { className: "h-4 w-4" })
1362
- }
1363
- ),
1364
- /* @__PURE__ */ jsxRuntime.jsx(
1365
- ui.Button,
1366
- {
1367
- type: "button",
1368
- variant: "secondary",
1369
- size: "small",
1370
- onClick: () => insertMarkdown("```\n", "\n```"),
1371
- title: "Code Block",
1372
- children: "Code"
1373
- }
1374
- )
1283
+ } else if (response.status !== 404) {
1284
+ console.error("Failed to fetch campaign detail");
1285
+ }
1286
+ } catch (error) {
1287
+ console.error("Error fetching campaign detail:", error);
1288
+ } finally {
1289
+ setIsLoading(false);
1290
+ }
1291
+ };
1292
+ const handleSave = async () => {
1293
+ setIsSaving(true);
1294
+ try {
1295
+ const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
1296
+ method: "POST",
1297
+ headers: {
1298
+ "Content-Type": "application/json"
1299
+ },
1300
+ body: JSON.stringify(formData),
1301
+ credentials: "include"
1302
+ });
1303
+ if (response.ok) {
1304
+ const data = await response.json();
1305
+ setCampaignDetail(data.campaign_detail);
1306
+ ui.toast.success("Campaign details saved successfully");
1307
+ } else {
1308
+ const errorData = await response.json();
1309
+ ui.toast.error(errorData.error || "Failed to save campaign details");
1310
+ }
1311
+ } catch (error) {
1312
+ console.error("Error saving campaign detail:", error);
1313
+ ui.toast.error("Error saving campaign details");
1314
+ } finally {
1315
+ setIsSaving(false);
1316
+ }
1317
+ };
1318
+ if (isLoading) {
1319
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-8 text-center", children: "Loading campaign details..." }) });
1320
+ }
1321
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1322
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "space-y-6", children: [
1323
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1324
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1325
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Campaign Details" }),
1326
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-ui-fg-subtle", children: campaignName })
1327
+ ] }),
1328
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSave, isLoading: isSaving, children: "Save Details" })
1375
1329
  ] }),
1376
- /* @__PURE__ */ jsxRuntime.jsx(
1377
- ui.Textarea,
1330
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1331
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Images" }),
1332
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
1333
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1334
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Main Campaign Image" }),
1335
+ /* @__PURE__ */ jsxRuntime.jsx(
1336
+ "div",
1337
+ {
1338
+ className: ui.clx(
1339
+ "relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
1340
+ (campaignDetail == null ? void 0 : campaignDetail.image_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
1341
+ ),
1342
+ children: (campaignDetail == null ? void 0 : campaignDetail.image_url) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1343
+ /* @__PURE__ */ jsxRuntime.jsx(
1344
+ "img",
1345
+ {
1346
+ src: campaignDetail.image_url,
1347
+ alt: "Campaign",
1348
+ className: "h-full w-full object-cover",
1349
+ onError: (e) => {
1350
+ console.error(
1351
+ "Failed to load image:",
1352
+ campaignDetail.image_url
1353
+ );
1354
+ e.currentTarget.style.display = "none";
1355
+ }
1356
+ }
1357
+ ),
1358
+ /* @__PURE__ */ jsxRuntime.jsxs(
1359
+ ui.Button,
1360
+ {
1361
+ variant: "secondary",
1362
+ size: "small",
1363
+ className: "absolute bottom-2 right-2",
1364
+ onClick: () => setShowImageUploader("image"),
1365
+ children: [
1366
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "mr-1" }),
1367
+ "Change"
1368
+ ]
1369
+ }
1370
+ )
1371
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
1372
+ ui.Button,
1373
+ {
1374
+ variant: "secondary",
1375
+ onClick: () => setShowImageUploader("image"),
1376
+ children: [
1377
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "mr-2" }),
1378
+ "Upload Image"
1379
+ ]
1380
+ }
1381
+ )
1382
+ }
1383
+ )
1384
+ ] }),
1385
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1386
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Thumbnail" }),
1387
+ /* @__PURE__ */ jsxRuntime.jsx(
1388
+ "div",
1389
+ {
1390
+ className: ui.clx(
1391
+ "relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
1392
+ (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
1393
+ ),
1394
+ children: (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1395
+ /* @__PURE__ */ jsxRuntime.jsx(
1396
+ "img",
1397
+ {
1398
+ src: campaignDetail.thumbnail_url,
1399
+ alt: "Thumbnail",
1400
+ className: "h-full w-full object-cover",
1401
+ onError: (e) => {
1402
+ console.error(
1403
+ "Failed to load thumbnail:",
1404
+ campaignDetail.thumbnail_url
1405
+ );
1406
+ e.currentTarget.style.display = "none";
1407
+ }
1408
+ }
1409
+ ),
1410
+ /* @__PURE__ */ jsxRuntime.jsxs(
1411
+ ui.Button,
1412
+ {
1413
+ variant: "secondary",
1414
+ size: "small",
1415
+ className: "absolute bottom-2 right-2",
1416
+ onClick: () => setShowImageUploader("thumbnail"),
1417
+ children: [
1418
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "mr-1" }),
1419
+ "Change"
1420
+ ]
1421
+ }
1422
+ )
1423
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
1424
+ ui.Button,
1425
+ {
1426
+ variant: "secondary",
1427
+ onClick: () => setShowImageUploader("thumbnail"),
1428
+ children: [
1429
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "mr-2" }),
1430
+ "Upload Thumbnail"
1431
+ ]
1432
+ }
1433
+ )
1434
+ }
1435
+ )
1436
+ ] })
1437
+ ] })
1438
+ ] }),
1439
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx(
1440
+ MarkdownEditor,
1378
1441
  {
1379
- id: `markdown-${label}`,
1380
- value,
1381
- onChange: (e) => onChange(e.target.value),
1382
- placeholder,
1383
- rows,
1384
- className: "font-mono text-sm"
1442
+ label: "Campaign Detail Content",
1443
+ value: formData.detail_content,
1444
+ onChange: (value) => setFormData({ ...formData, detail_content: value }),
1445
+ placeholder: "Enter campaign details in Markdown format. You can include images, links, code blocks, and more...",
1446
+ helpText: "Supports Markdown and code syntax",
1447
+ rows: 12,
1448
+ showPreview: true
1385
1449
  }
1386
- )
1450
+ ) }),
1451
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx(
1452
+ MarkdownEditor,
1453
+ {
1454
+ label: "Terms and Conditions",
1455
+ value: formData.terms_and_conditions,
1456
+ onChange: (value) => setFormData({ ...formData, terms_and_conditions: value }),
1457
+ placeholder: "Enter terms and conditions for this campaign...",
1458
+ helpText: "Optional - Campaign specific terms",
1459
+ rows: 8,
1460
+ showPreview: true
1461
+ }
1462
+ ) }),
1463
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1464
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Call to Action Link" }),
1465
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
1466
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1467
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "link-url", children: "Link URL" }),
1468
+ /* @__PURE__ */ jsxRuntime.jsx(
1469
+ ui.Input,
1470
+ {
1471
+ id: "link-url",
1472
+ type: "url",
1473
+ value: formData.link_url,
1474
+ onChange: (e) => setFormData({ ...formData, link_url: e.target.value }),
1475
+ placeholder: "https://example.com/campaign"
1476
+ }
1477
+ )
1478
+ ] }),
1479
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1480
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "link-text", children: "Link Text" }),
1481
+ /* @__PURE__ */ jsxRuntime.jsx(
1482
+ ui.Input,
1483
+ {
1484
+ id: "link-text",
1485
+ value: formData.link_text,
1486
+ onChange: (e) => setFormData({ ...formData, link_text: e.target.value }),
1487
+ placeholder: "Shop Now"
1488
+ }
1489
+ )
1490
+ ] })
1491
+ ] })
1492
+ ] }),
1493
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1494
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "SEO & Metadata" }),
1495
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1496
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1497
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "meta-title", children: "Meta Title" }),
1498
+ /* @__PURE__ */ jsxRuntime.jsx(
1499
+ ui.Input,
1500
+ {
1501
+ id: "meta-title",
1502
+ value: formData.meta_title,
1503
+ onChange: (e) => setFormData({ ...formData, meta_title: e.target.value }),
1504
+ placeholder: "Campaign meta title for SEO"
1505
+ }
1506
+ )
1507
+ ] }),
1508
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1509
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "meta-description", children: "Meta Description" }),
1510
+ /* @__PURE__ */ jsxRuntime.jsx(
1511
+ ui.Input,
1512
+ {
1513
+ id: "meta-description",
1514
+ value: formData.meta_description,
1515
+ onChange: (e) => setFormData({
1516
+ ...formData,
1517
+ meta_description: e.target.value
1518
+ }),
1519
+ placeholder: "Campaign meta description for SEO"
1520
+ }
1521
+ )
1522
+ ] }),
1523
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1524
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "meta-keywords", children: "Meta Keywords" }),
1525
+ /* @__PURE__ */ jsxRuntime.jsx(
1526
+ ui.Input,
1527
+ {
1528
+ id: "meta-keywords",
1529
+ value: formData.meta_keywords,
1530
+ onChange: (e) => setFormData({
1531
+ ...formData,
1532
+ meta_keywords: e.target.value
1533
+ }),
1534
+ placeholder: "keyword1, keyword2, keyword3"
1535
+ }
1536
+ ),
1537
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Comma-separated keywords for SEO" })
1538
+ ] }),
1539
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1540
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "display-order", children: "Display Order" }),
1541
+ /* @__PURE__ */ jsxRuntime.jsx(
1542
+ ui.Input,
1543
+ {
1544
+ id: "display-order",
1545
+ type: "number",
1546
+ value: formData.display_order,
1547
+ onChange: (e) => setFormData({
1548
+ ...formData,
1549
+ display_order: parseInt(e.target.value) || 0
1550
+ }),
1551
+ placeholder: "0"
1552
+ }
1553
+ ),
1554
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Lower numbers appear first in listings" })
1555
+ ] })
1556
+ ] })
1557
+ ] }),
1558
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end border-t pt-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSave, isLoading: isSaving, children: "Save Campaign Details" }) })
1387
1559
  ] }),
1388
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-ui-fg-subtle", children: "Supports Markdown formatting: **bold**, *italic*, `code`, ```code blocks```, [links](url), ![images](url), and lists" })
1560
+ showImageUploader && /* @__PURE__ */ jsxRuntime.jsx(
1561
+ CampaignImageUploader,
1562
+ {
1563
+ campaignId,
1564
+ imageType: showImageUploader,
1565
+ currentImageUrl: showImageUploader === "image" ? campaignDetail == null ? void 0 : campaignDetail.image_url : campaignDetail == null ? void 0 : campaignDetail.thumbnail_url,
1566
+ onClose: () => setShowImageUploader(null),
1567
+ onSuccess: fetchCampaignDetail
1568
+ }
1569
+ )
1389
1570
  ] });
1390
1571
  };
1391
- const CampaignImageUploader = ({
1392
- campaignId,
1393
- imageType,
1394
- currentImageUrl,
1395
- onClose,
1396
- onSuccess
1397
- }) => {
1398
- const [displayImage, setDisplayImage] = react.useState(currentImageUrl || null);
1399
- const [isUploading, setIsUploading] = react.useState(false);
1400
- const [isDragging, setIsDragging] = react.useState(false);
1401
- const [error, setError] = react.useState(null);
1402
- const [previewUrl, setPreviewUrl] = react.useState(null);
1403
- const isThumbnail = imageType === "thumbnail";
1404
- const maxSize = 5;
1405
- const allowedTypes = ["image/jpeg", "image/png", "image/gif", "image/webp"];
1406
- const formatFileTypes = () => {
1407
- return "JPEG, PNG, GIF, or WebP";
1408
- };
1409
- const validateFile = (file) => {
1410
- if (!allowedTypes.includes(file.type)) {
1411
- setError(`Please upload a valid image file (${formatFileTypes()})`);
1412
- return false;
1413
- }
1414
- if (file.size > maxSize * 1024 * 1024) {
1415
- setError(`File size must be less than ${maxSize}MB`);
1416
- return false;
1417
- }
1418
- return true;
1572
+ const CouponDetail = () => {
1573
+ var _a, _b, _c, _d, _e, _f, _g;
1574
+ const { id } = reactRouterDom.useParams();
1575
+ const navigate = reactRouterDom.useNavigate();
1576
+ const [isEditing, setIsEditing] = react.useState(false);
1577
+ const { data, isLoading, refetch } = useCouponById(id ?? "");
1578
+ if (isLoading) {
1579
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent" }) });
1580
+ }
1581
+ if (!(data == null ? void 0 : data.campaign)) {
1582
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Coupon not found" }) });
1583
+ }
1584
+ const campaign = data.campaign;
1585
+ const promotion = (_a = campaign.promotions) == null ? void 0 : _a[0];
1586
+ const initialValues = {
1587
+ name: campaign.name ?? "",
1588
+ description: campaign.description ?? "",
1589
+ type: "coupon",
1590
+ code: (promotion == null ? void 0 : promotion.code) ?? "",
1591
+ discount_type: ((_b = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _b.type) ?? "percentage",
1592
+ discount_value: Number(((_c = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _c.value) ?? 0),
1593
+ currency_code: ((_d = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _d.currency_code) ?? void 0,
1594
+ starts_at: campaign.starts_at ? dayjs__default.default(campaign.starts_at).format("YYYY-MM-DDTHH:mm") : void 0,
1595
+ ends_at: campaign.ends_at ? dayjs__default.default(campaign.ends_at).format("YYYY-MM-DDTHH:mm") : void 0,
1596
+ allocation: ((_e = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _e.allocation) === "total" ? "across" : ((_f = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _f.allocation) ?? "across",
1597
+ target_type: ((_g = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _g.target_type) ?? "order"
1419
1598
  };
1420
- const handleFileUpload = async (file) => {
1421
- var _a, _b;
1422
- if (!validateFile(file)) return;
1423
- setError(null);
1424
- const reader = new FileReader();
1425
- reader.onloadend = () => {
1426
- setPreviewUrl(reader.result);
1427
- };
1428
- reader.readAsDataURL(file);
1429
- setIsUploading(true);
1430
- const formData = new FormData();
1431
- formData.append("file", file);
1432
- try {
1433
- const response = await fetch(
1434
- `/admin/campaigns/${campaignId}/${imageType}`,
1435
- {
1436
- method: "POST",
1437
- body: formData,
1438
- credentials: "include"
1439
- }
1440
- );
1441
- if (response.ok) {
1442
- const result = await response.json();
1443
- const newImageUrl = imageType === "image" ? (_a = result.campaign_detail) == null ? void 0 : _a.image_url : (_b = result.campaign_detail) == null ? void 0 : _b.thumbnail_url;
1444
- setDisplayImage(newImageUrl);
1445
- setPreviewUrl(null);
1446
- onSuccess == null ? void 0 : onSuccess();
1447
- setTimeout(() => {
1448
- onClose();
1449
- }, 1e3);
1450
- } else {
1451
- const errorData = await response.json();
1452
- setError(errorData.error || `Failed to upload ${imageType}`);
1453
- setPreviewUrl(null);
1454
- }
1455
- } catch (err) {
1456
- setError(`Error uploading ${imageType}`);
1457
- setPreviewUrl(null);
1458
- console.error(`Error uploading ${imageType}:`, err);
1459
- } finally {
1460
- setIsUploading(false);
1599
+ const handleSubmit = async (formData) => {
1600
+ var _a2, _b2;
1601
+ if (!id) {
1602
+ return;
1461
1603
  }
1462
- };
1463
- const handleDelete = async () => {
1464
- if (!confirm(`Are you sure you want to delete the ${imageType}?`)) return;
1465
- setIsUploading(true);
1466
- setError(null);
1467
1604
  try {
1468
- const response = await fetch(
1469
- `/admin/campaigns/${campaignId}/${imageType}`,
1470
- {
1471
- method: "DELETE",
1472
- credentials: "include"
1473
- }
1474
- );
1475
- if (response.ok) {
1476
- setDisplayImage(null);
1477
- setPreviewUrl(null);
1478
- onSuccess == null ? void 0 : onSuccess();
1479
- setTimeout(() => {
1480
- onClose();
1481
- }, 500);
1482
- } else {
1483
- const errorData = await response.json();
1484
- setError(errorData.error || `Failed to delete ${imageType}`);
1605
+ await axios__default.default.put(`/admin/coupons/${id}`, {
1606
+ ...formData,
1607
+ starts_at: new Date(formData.starts_at).toUTCString(),
1608
+ ends_at: new Date(formData.ends_at).toUTCString(),
1609
+ currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
1610
+ });
1611
+ ui.toast.success("Coupon updated successfully");
1612
+ setIsEditing(false);
1613
+ refetch();
1614
+ } catch (error) {
1615
+ let message = "Failed to update coupon";
1616
+ if (axios__default.default.isAxiosError(error)) {
1617
+ message = ((_b2 = (_a2 = error.response) == null ? void 0 : _a2.data) == null ? void 0 : _b2.message) ?? message;
1618
+ } else if (error instanceof Error) {
1619
+ message = error.message;
1485
1620
  }
1486
- } catch (err) {
1487
- setError(`Error deleting ${imageType}`);
1488
- console.error(`Error deleting ${imageType}:`, err);
1489
- } finally {
1490
- setIsUploading(false);
1621
+ ui.toast.error("Failed to update coupon", {
1622
+ description: message
1623
+ });
1491
1624
  }
1492
1625
  };
1493
- const handleDragEnter = react.useCallback((e) => {
1494
- e.preventDefault();
1495
- e.stopPropagation();
1496
- setIsDragging(true);
1497
- }, []);
1498
- const handleDragLeave = react.useCallback((e) => {
1499
- e.preventDefault();
1500
- e.stopPropagation();
1501
- setIsDragging(false);
1502
- }, []);
1503
- const handleDragOver = react.useCallback((e) => {
1504
- e.preventDefault();
1505
- e.stopPropagation();
1506
- }, []);
1507
- const handleDrop = react.useCallback((e) => {
1508
- var _a;
1509
- e.preventDefault();
1510
- e.stopPropagation();
1511
- setIsDragging(false);
1512
- const file = (_a = e.dataTransfer.files) == null ? void 0 : _a[0];
1513
- if (file) {
1514
- handleFileUpload(file);
1515
- }
1516
- }, []);
1517
- const imageToDisplay = previewUrl || displayImage;
1518
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-2xl rounded-lg bg-ui-bg-base p-6", children: [
1626
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
1519
1627
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
1520
1628
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1521
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Heading, { level: "h2", children: [
1522
- "Upload Campaign ",
1523
- isThumbnail ? "Thumbnail" : "Image"
1524
- ] }),
1525
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: isThumbnail ? "Thumbnail for campaign listing" : "Main campaign image" })
1629
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold mb-1", children: campaign.name }),
1630
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle", children: "Manage coupon configuration and content" })
1526
1631
  ] }),
1527
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(icons.X, {}) })
1632
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1633
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/coupons"), children: "Back to Coupons" }),
1634
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
1635
+ ] })
1528
1636
  ] }),
1529
- error && /* @__PURE__ */ jsxRuntime.jsx(ui.Alert, { variant: "error", dismissible: true, className: "mb-4", children: error }),
1530
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1531
- imageToDisplay ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1532
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative overflow-hidden rounded-lg border border-ui-border-base bg-ui-bg-subtle", children: [
1533
- /* @__PURE__ */ jsxRuntime.jsx(
1534
- "img",
1535
- {
1536
- src: imageToDisplay,
1537
- alt: `Campaign ${imageType}`,
1538
- className: ui.clx(
1539
- "w-full object-contain",
1540
- isThumbnail ? "h-32" : "h-64"
1541
- )
1542
- }
1543
- ),
1544
- isUploading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-white", children: "Uploading..." }) })
1545
- ] }),
1546
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1547
- /* @__PURE__ */ jsxRuntime.jsxs(
1548
- ui.Button,
1549
- {
1550
- variant: "secondary",
1551
- disabled: isUploading,
1552
- onClick: () => {
1553
- var _a;
1554
- return (_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click();
1555
- },
1556
- children: [
1557
- /* @__PURE__ */ jsxRuntime.jsx(icons.CloudArrowUp, { className: "mr-2" }),
1558
- "Replace"
1559
- ]
1560
- }
1561
- ),
1562
- /* @__PURE__ */ jsxRuntime.jsxs(
1563
- ui.Button,
1564
- {
1565
- variant: "danger",
1566
- disabled: isUploading,
1567
- onClick: handleDelete,
1568
- children: [
1569
- /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "mr-2" }),
1570
- "Delete"
1571
- ]
1572
- }
1573
- )
1574
- ] })
1575
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
1576
- "div",
1637
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { defaultValue: "settings", children: [
1638
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
1639
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "settings", children: "Coupon Settings" }),
1640
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "details", children: "Coupon Details" })
1641
+ ] }),
1642
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
1643
+ CouponForm,
1577
1644
  {
1578
- className: ui.clx(
1579
- "flex flex-col items-center justify-center rounded-lg border-2 border-dashed transition-colors",
1580
- isThumbnail ? "h-48" : "h-64",
1581
- isDragging ? "border-ui-border-interactive bg-ui-bg-highlight" : "border-ui-border-base bg-ui-bg-subtle",
1582
- !isUploading && "cursor-pointer"
1583
- ),
1584
- onDragEnter: handleDragEnter,
1585
- onDragLeave: handleDragLeave,
1586
- onDragOver: handleDragOver,
1587
- onDrop: handleDrop,
1588
- onClick: () => {
1589
- var _a;
1590
- return !isUploading && ((_a = document.getElementById(`${imageType}-file-input`)) == null ? void 0 : _a.click());
1591
- },
1592
- children: [
1593
- /* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "mb-4 h-12 w-12 text-ui-fg-subtle" }),
1594
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-2 text-lg font-medium", children: isDragging ? `Drop ${imageType} here` : `Upload ${isThumbnail ? "Thumbnail" : "Image"}` }),
1595
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mb-4 text-center text-ui-fg-subtle", children: "Drag and drop an image here, or click to select" }),
1596
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-sm text-ui-fg-subtle", children: [
1597
- formatFileTypes(),
1598
- " • Max ",
1599
- maxSize,
1600
- "MB"
1601
- ] }),
1602
- isThumbnail && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: 16:9 aspect ratio, minimum 400x225px" }),
1603
- !isThumbnail && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "mt-2 text-xs text-ui-fg-subtle", children: "Recommended: High resolution, minimum 1200x600px" }),
1604
- isUploading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { children: "Uploading..." }) })
1605
- ]
1645
+ initialData: initialValues,
1646
+ onSubmit: handleSubmit,
1647
+ onCancel: () => navigate("/coupons"),
1648
+ disabled: !isEditing
1606
1649
  }
1607
- ),
1608
- /* @__PURE__ */ jsxRuntime.jsx(
1609
- "input",
1650
+ ) }),
1651
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
1652
+ CampaignDetailForm,
1610
1653
  {
1611
- id: `${imageType}-file-input`,
1612
- type: "file",
1613
- accept: allowedTypes.join(","),
1614
- onChange: (e) => {
1615
- var _a;
1616
- const file = (_a = e.target.files) == null ? void 0 : _a[0];
1617
- if (file) handleFileUpload(file);
1618
- e.target.value = "";
1619
- },
1620
- className: "hidden"
1654
+ campaignId: campaign.id,
1655
+ campaignName: campaign.name ?? ""
1621
1656
  }
1622
- )
1623
- ] }),
1624
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 flex items-center justify-between border-t pt-4", children: [
1625
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm text-ui-fg-subtle", children: isThumbnail ? "Thumbnail will be displayed in campaign listings" : "Main image for the campaign detail page" }),
1626
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: onClose, children: "Close" })
1657
+ ) })
1627
1658
  ] })
1628
- ] }) });
1659
+ ] });
1660
+ };
1661
+ const config$1 = adminSdk.defineRouteConfig({
1662
+ label: "Coupon Detail",
1663
+ icon: icons.Tag
1664
+ });
1665
+ const CouponCreate = () => {
1666
+ const navigate = reactRouterDom.useNavigate();
1667
+ const handleSubmit = async (formData) => {
1668
+ var _a, _b;
1669
+ try {
1670
+ await axios__default.default.post("/admin/coupons", {
1671
+ ...formData,
1672
+ starts_at: new Date(formData.starts_at).toUTCString(),
1673
+ ends_at: new Date(formData.ends_at).toUTCString(),
1674
+ currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
1675
+ });
1676
+ ui.toast.success("Coupon created successfully");
1677
+ navigate("/coupons");
1678
+ } catch (error) {
1679
+ let message = "Failed to create coupon";
1680
+ if (axios__default.default.isAxiosError(error)) {
1681
+ message = ((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) ?? message;
1682
+ } else if (error instanceof Error) {
1683
+ message = error.message;
1684
+ }
1685
+ ui.toast.error("Failed to create coupon", {
1686
+ description: message
1687
+ });
1688
+ }
1689
+ };
1690
+ return /* @__PURE__ */ jsxRuntime.jsx(
1691
+ CouponForm,
1692
+ {
1693
+ onSubmit: handleSubmit,
1694
+ onCancel: () => navigate("/coupons")
1695
+ }
1696
+ );
1697
+ };
1698
+ const sdk = new Medusa__default.default({
1699
+ baseUrl: "/",
1700
+ debug: false,
1701
+ auth: {
1702
+ type: "session"
1703
+ }
1704
+ });
1705
+ const columnHelper = ui.createDataTableColumnHelper();
1706
+ const columns = [
1707
+ columnHelper.accessor("title", {
1708
+ header: "Title",
1709
+ cell: (info) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "large", className: "py-2", children: info.getValue() })
1710
+ }),
1711
+ columnHelper.accessor("description", {
1712
+ header: "Description",
1713
+ cell: (info) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "large", className: "py-2", children: info.getValue() })
1714
+ })
1715
+ ];
1716
+ const ProductSelector = ({
1717
+ selectedProductIds,
1718
+ onSelectProduct
1719
+ }) => {
1720
+ const [search, setSearch] = react.useState("");
1721
+ const debouncedSearchHandler = lodash.debounce((value) => {
1722
+ setSearch(value);
1723
+ }, 500);
1724
+ const [products, setProducts] = react.useState(null);
1725
+ const [isLoading, setIsLoading] = react.useState(true);
1726
+ react.useEffect(() => {
1727
+ const fetchProducts = async () => {
1728
+ setIsLoading(true);
1729
+ try {
1730
+ const result = await sdk.admin.product.list({
1731
+ q: search,
1732
+ limit: 100
1733
+ });
1734
+ setProducts(result);
1735
+ } catch (error) {
1736
+ console.error("Failed to fetch products:", error);
1737
+ } finally {
1738
+ setIsLoading(false);
1739
+ }
1740
+ };
1741
+ fetchProducts();
1742
+ }, [selectedProductIds, search]);
1743
+ const selectableProducts = react.useMemo(() => {
1744
+ var _a;
1745
+ return (_a = products == null ? void 0 : products.products) == null ? void 0 : _a.filter(
1746
+ (product) => !selectedProductIds.includes(product.id)
1747
+ );
1748
+ }, [products == null ? void 0 : products.products, selectedProductIds]);
1749
+ const table = ui.useDataTable({
1750
+ data: selectableProducts || [],
1751
+ columns,
1752
+ getRowId: (product) => product.id,
1753
+ onRowClick: (_, row) => {
1754
+ onSelectProduct == null ? void 0 : onSelectProduct(row.original);
1755
+ }
1756
+ });
1757
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
1758
+ /* @__PURE__ */ jsxRuntime.jsx(
1759
+ ui.Input,
1760
+ {
1761
+ className: "text-lg py-2",
1762
+ placeholder: "Search products...",
1763
+ onChange: (e) => debouncedSearchHandler(e.target.value)
1764
+ }
1765
+ ),
1766
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { instance: table, children: /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {}) })
1767
+ ] });
1629
1768
  };
1630
- const CampaignDetailForm = ({
1631
- campaignId,
1632
- campaignName
1769
+ const customCampaignSchema = z__default.default.object({
1770
+ name: z__default.default.string().min(1, "Name is required"),
1771
+ description: z__default.default.string().min(1, "Description is required"),
1772
+ type: z__default.default.literal("flash-sale"),
1773
+ starts_at: z__default.default.string().min(1, "Start date is required"),
1774
+ ends_at: z__default.default.string().min(1, "End date is required")
1775
+ });
1776
+ const campaignProductSchema = z__default.default.object({
1777
+ product: z__default.default.object({
1778
+ id: z__default.default.string(),
1779
+ title: z__default.default.string()
1780
+ }),
1781
+ discountType: z__default.default.enum([
1782
+ "percentage"
1783
+ // , "fixed"
1784
+ ]),
1785
+ discountValue: z__default.default.number().min(1),
1786
+ limit: z__default.default.number().min(1),
1787
+ maxQty: z__default.default.number().min(1)
1788
+ });
1789
+ const campaignDataSchema = customCampaignSchema.extend({
1790
+ products: z__default.default.array(campaignProductSchema).min(1, "At least one product is required")
1791
+ }).refine(
1792
+ (data) => new Date(data.starts_at) < new Date(data.ends_at),
1793
+ "End date must be after start date"
1794
+ );
1795
+ const FlashSaleForm = ({
1796
+ initialData,
1797
+ initialProducts,
1798
+ onSubmit,
1799
+ onCancel,
1800
+ disabled = false
1633
1801
  }) => {
1634
- const [campaignDetail, setCampaignDetail] = react.useState(
1635
- null
1636
- );
1637
- const [isLoading, setIsLoading] = react.useState(true);
1638
- const [isSaving, setIsSaving] = react.useState(false);
1639
- const [showImageUploader, setShowImageUploader] = react.useState(null);
1640
- const [formData, setFormData] = react.useState({
1641
- detail_content: "",
1642
- terms_and_conditions: "",
1643
- meta_title: "",
1644
- meta_description: "",
1645
- meta_keywords: "",
1646
- link_url: "",
1647
- link_text: "",
1648
- display_order: 0
1802
+ var _a;
1803
+ const {
1804
+ control,
1805
+ register,
1806
+ handleSubmit,
1807
+ watch,
1808
+ setValue,
1809
+ formState: { errors }
1810
+ } = reactHookForm.useForm({
1811
+ resolver: zod.zodResolver(campaignDataSchema),
1812
+ defaultValues: {
1813
+ name: (initialData == null ? void 0 : initialData.name) || "",
1814
+ description: (initialData == null ? void 0 : initialData.description) || "",
1815
+ type: "flash-sale",
1816
+ starts_at: (initialData == null ? void 0 : initialData.starts_at) || "",
1817
+ ends_at: (initialData == null ? void 0 : initialData.ends_at) || "",
1818
+ products: initialProducts ? Array.from(initialProducts.values()) : []
1819
+ }
1649
1820
  });
1650
- react.useEffect(() => {
1651
- fetchCampaignDetail();
1652
- }, [campaignId]);
1653
- const fetchCampaignDetail = async () => {
1654
- setIsLoading(true);
1655
- try {
1656
- const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
1657
- credentials: "include"
1658
- });
1659
- if (response.ok) {
1660
- const data = await response.json();
1661
- if (data.campaign_detail) {
1662
- console.log("Campaign detail loaded:", {
1663
- image_url: data.campaign_detail.image_url,
1664
- thumbnail_url: data.campaign_detail.thumbnail_url,
1665
- decoded_image: data.campaign_detail.image_url ? decodeURIComponent(data.campaign_detail.image_url) : null,
1666
- decoded_thumbnail: data.campaign_detail.thumbnail_url ? decodeURIComponent(data.campaign_detail.thumbnail_url) : null
1667
- });
1668
- setCampaignDetail(data.campaign_detail);
1669
- setFormData({
1670
- detail_content: data.campaign_detail.detail_content || "",
1671
- terms_and_conditions: data.campaign_detail.terms_and_conditions || "",
1672
- meta_title: data.campaign_detail.meta_title || "",
1673
- meta_description: data.campaign_detail.meta_description || "",
1674
- meta_keywords: data.campaign_detail.meta_keywords || "",
1675
- link_url: data.campaign_detail.link_url || "",
1676
- link_text: data.campaign_detail.link_text || "",
1677
- display_order: data.campaign_detail.display_order || 0
1678
- });
1679
- }
1680
- } else if (response.status !== 404) {
1681
- console.error("Failed to fetch campaign detail");
1682
- }
1683
- } catch (error) {
1684
- console.error("Error fetching campaign detail:", error);
1685
- } finally {
1686
- setIsLoading(false);
1821
+ const { fields, append, remove, update } = reactHookForm.useFieldArray({
1822
+ control,
1823
+ name: "products"
1824
+ });
1825
+ const [openProductModal, setOpenProductModal] = react.useState(false);
1826
+ const startsAt = watch("starts_at");
1827
+ const endsAt = watch("ends_at");
1828
+ const handleDateTimeChange = (field, type, value) => {
1829
+ const currentValue = watch(field);
1830
+ if (type === "date") {
1831
+ const time = currentValue ? dayjs__default.default(currentValue).format("HH:mm") : field === "starts_at" ? "00:00" : "23:59";
1832
+ setValue(field, `${value}T${time}`);
1833
+ } else {
1834
+ const date = currentValue ? dayjs__default.default(currentValue).format("YYYY-MM-DD") : dayjs__default.default().format("YYYY-MM-DD");
1835
+ setValue(field, `${date}T${value}`);
1687
1836
  }
1688
1837
  };
1689
- const handleSave = async () => {
1690
- setIsSaving(true);
1691
- try {
1692
- const response = await fetch(`/admin/campaigns/${campaignId}/detail`, {
1693
- method: "POST",
1694
- headers: {
1695
- "Content-Type": "application/json"
1696
- },
1697
- body: JSON.stringify(formData),
1698
- credentials: "include"
1699
- });
1700
- if (response.ok) {
1701
- const data = await response.json();
1702
- setCampaignDetail(data.campaign_detail);
1703
- ui.toast.success("Campaign details saved successfully");
1704
- } else {
1705
- const errorData = await response.json();
1706
- ui.toast.error(errorData.error || "Failed to save campaign details");
1838
+ const onFormSubmit = (data) => {
1839
+ onSubmit(data);
1840
+ };
1841
+ const onFormError = () => {
1842
+ const errorMessages = Object.entries(errors).map(([key, value]) => {
1843
+ var _a2;
1844
+ if (key === "products" && Array.isArray(value)) {
1845
+ return value.map(
1846
+ (item, index) => item ? `Product ${index + 1}: ${Object.values(item).join(", ")}` : ""
1847
+ ).filter(Boolean).join(", ");
1707
1848
  }
1708
- } catch (error) {
1709
- console.error("Error saving campaign detail:", error);
1710
- ui.toast.error("Error saving campaign details");
1711
- } finally {
1712
- setIsSaving(false);
1713
- }
1849
+ return (value == null ? void 0 : value.message) || ((_a2 = value == null ? void 0 : value.root) == null ? void 0 : _a2.message);
1850
+ }).filter(Boolean).join(", ");
1851
+ ui.toast.error("Invalid data", {
1852
+ description: errorMessages
1853
+ });
1714
1854
  };
1715
- if (isLoading) {
1716
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-8 text-center", children: "Loading campaign details..." }) });
1717
- }
1718
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1719
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "space-y-6", children: [
1720
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1721
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1722
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Campaign Details" }),
1723
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-ui-fg-subtle", children: campaignName })
1724
- ] }),
1725
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSave, isLoading: isSaving, children: "Save Details" })
1726
- ] }),
1727
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1728
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Images" }),
1729
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
1730
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1731
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Main Campaign Image" }),
1732
- /* @__PURE__ */ jsxRuntime.jsx(
1733
- "div",
1734
- {
1735
- className: ui.clx(
1736
- "relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
1737
- (campaignDetail == null ? void 0 : campaignDetail.image_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
1738
- ),
1739
- children: (campaignDetail == null ? void 0 : campaignDetail.image_url) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1740
- /* @__PURE__ */ jsxRuntime.jsx(
1741
- "img",
1742
- {
1743
- src: campaignDetail.image_url,
1744
- alt: "Campaign",
1745
- className: "h-full w-full object-cover",
1746
- onError: (e) => {
1747
- console.error(
1748
- "Failed to load image:",
1749
- campaignDetail.image_url
1750
- );
1751
- e.currentTarget.style.display = "none";
1752
- }
1753
- }
1754
- ),
1755
- /* @__PURE__ */ jsxRuntime.jsxs(
1756
- ui.Button,
1757
- {
1758
- variant: "secondary",
1759
- size: "small",
1760
- className: "absolute bottom-2 right-2",
1761
- onClick: () => setShowImageUploader("image"),
1762
- children: [
1763
- /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "mr-1" }),
1764
- "Change"
1765
- ]
1766
- }
1767
- )
1768
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
1769
- ui.Button,
1855
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
1856
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
1857
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-xl font-semibold", children: "Flash sale campaign" }),
1858
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "transparent", onClick: onCancel, children: "Cancel" })
1859
+ ] }),
1860
+ /* @__PURE__ */ jsxRuntime.jsxs(
1861
+ "form",
1862
+ {
1863
+ onSubmit: handleSubmit(onFormSubmit, onFormError),
1864
+ className: "space-y-4 my-8",
1865
+ children: [
1866
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1867
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Name" }),
1868
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...register("name"), disabled }),
1869
+ errors.name && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.name.message })
1870
+ ] }),
1871
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1872
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Description" }),
1873
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...register("description"), disabled }),
1874
+ errors.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.description.message })
1875
+ ] }),
1876
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
1877
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1878
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Start Date" }),
1879
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1880
+ /* @__PURE__ */ jsxRuntime.jsx(
1881
+ ui.Input,
1882
+ {
1883
+ type: "date",
1884
+ value: startsAt ? dayjs__default.default(startsAt).format("YYYY-MM-DD") : "",
1885
+ onChange: (e) => handleDateTimeChange("starts_at", "date", e.target.value),
1886
+ disabled,
1887
+ className: "flex-1"
1888
+ }
1889
+ ),
1890
+ /* @__PURE__ */ jsxRuntime.jsx(
1891
+ ui.Input,
1770
1892
  {
1771
- variant: "secondary",
1772
- onClick: () => setShowImageUploader("image"),
1773
- children: [
1774
- /* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "mr-2" }),
1775
- "Upload Image"
1776
- ]
1893
+ type: "time",
1894
+ value: startsAt ? dayjs__default.default(startsAt).format("HH:mm") : "",
1895
+ onChange: (e) => handleDateTimeChange("starts_at", "time", e.target.value),
1896
+ disabled,
1897
+ className: "w-32"
1777
1898
  }
1778
1899
  )
1779
- }
1780
- )
1781
- ] }),
1782
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1783
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Thumbnail" }),
1784
- /* @__PURE__ */ jsxRuntime.jsx(
1785
- "div",
1786
- {
1787
- className: ui.clx(
1788
- "relative flex h-64 items-center justify-center overflow-hidden rounded-lg border-2 border-dashed",
1789
- (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? "border-ui-border-base" : "border-ui-border-base bg-ui-bg-subtle"
1900
+ ] }),
1901
+ errors.starts_at && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.starts_at.message })
1902
+ ] }),
1903
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1904
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "End Date" }),
1905
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1906
+ /* @__PURE__ */ jsxRuntime.jsx(
1907
+ ui.Input,
1908
+ {
1909
+ type: "date",
1910
+ value: endsAt ? dayjs__default.default(endsAt).format("YYYY-MM-DD") : "",
1911
+ onChange: (e) => handleDateTimeChange("ends_at", "date", e.target.value),
1912
+ disabled,
1913
+ className: "flex-1"
1914
+ }
1790
1915
  ),
1791
- children: (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1792
- /* @__PURE__ */ jsxRuntime.jsx(
1793
- "img",
1794
- {
1795
- src: campaignDetail.thumbnail_url,
1796
- alt: "Thumbnail",
1797
- className: "h-full w-full object-cover",
1798
- onError: (e) => {
1799
- console.error(
1800
- "Failed to load thumbnail:",
1801
- campaignDetail.thumbnail_url
1802
- );
1803
- e.currentTarget.style.display = "none";
1804
- }
1805
- }
1806
- ),
1807
- /* @__PURE__ */ jsxRuntime.jsxs(
1808
- ui.Button,
1809
- {
1810
- variant: "secondary",
1811
- size: "small",
1812
- className: "absolute bottom-2 right-2",
1813
- onClick: () => setShowImageUploader("thumbnail"),
1814
- children: [
1815
- /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "mr-1" }),
1816
- "Change"
1817
- ]
1818
- }
1819
- )
1820
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
1821
- ui.Button,
1916
+ /* @__PURE__ */ jsxRuntime.jsx(
1917
+ ui.Input,
1822
1918
  {
1823
- variant: "secondary",
1824
- onClick: () => setShowImageUploader("thumbnail"),
1825
- children: [
1826
- /* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "mr-2" }),
1827
- "Upload Thumbnail"
1828
- ]
1919
+ type: "time",
1920
+ value: endsAt ? dayjs__default.default(endsAt).format("HH:mm") : "",
1921
+ onChange: (e) => handleDateTimeChange("ends_at", "time", e.target.value),
1922
+ disabled,
1923
+ className: "w-32"
1829
1924
  }
1830
1925
  )
1831
- }
1832
- )
1833
- ] })
1834
- ] })
1835
- ] }),
1836
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx(
1837
- MarkdownEditor,
1838
- {
1839
- label: "Campaign Detail Content",
1840
- value: formData.detail_content,
1841
- onChange: (value) => setFormData({ ...formData, detail_content: value }),
1842
- placeholder: "Enter campaign details in Markdown format. You can include images, links, code blocks, and more...",
1843
- helpText: "Supports Markdown and code syntax",
1844
- rows: 12,
1845
- showPreview: true
1846
- }
1847
- ) }),
1848
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx(
1849
- MarkdownEditor,
1850
- {
1851
- label: "Terms and Conditions",
1852
- value: formData.terms_and_conditions,
1853
- onChange: (value) => setFormData({ ...formData, terms_and_conditions: value }),
1854
- placeholder: "Enter terms and conditions for this campaign...",
1855
- helpText: "Optional - Campaign specific terms",
1856
- rows: 8,
1857
- showPreview: true
1858
- }
1859
- ) }),
1860
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1861
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Call to Action Link" }),
1862
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
1863
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1864
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "link-url", children: "Link URL" }),
1865
- /* @__PURE__ */ jsxRuntime.jsx(
1866
- ui.Input,
1867
- {
1868
- id: "link-url",
1869
- type: "url",
1870
- value: formData.link_url,
1871
- onChange: (e) => setFormData({ ...formData, link_url: e.target.value }),
1872
- placeholder: "https://example.com/campaign"
1873
- }
1874
- )
1875
- ] }),
1876
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1877
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "link-text", children: "Link Text" }),
1878
- /* @__PURE__ */ jsxRuntime.jsx(
1879
- ui.Input,
1880
- {
1881
- id: "link-text",
1882
- value: formData.link_text,
1883
- onChange: (e) => setFormData({ ...formData, link_text: e.target.value }),
1884
- placeholder: "Shop Now"
1885
- }
1886
- )
1887
- ] })
1888
- ] })
1889
- ] }),
1890
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1891
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "SEO & Metadata" }),
1892
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1893
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1894
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "meta-title", children: "Meta Title" }),
1895
- /* @__PURE__ */ jsxRuntime.jsx(
1896
- ui.Input,
1897
- {
1898
- id: "meta-title",
1899
- value: formData.meta_title,
1900
- onChange: (e) => setFormData({ ...formData, meta_title: e.target.value }),
1901
- placeholder: "Campaign meta title for SEO"
1902
- }
1903
- )
1926
+ ] }),
1927
+ errors.ends_at && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
1928
+ ] })
1904
1929
  ] }),
1905
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1906
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "meta-description", children: "Meta Description" }),
1930
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
1931
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Products" }),
1907
1932
  /* @__PURE__ */ jsxRuntime.jsx(
1908
- ui.Input,
1933
+ ui.Button,
1909
1934
  {
1910
- id: "meta-description",
1911
- value: formData.meta_description,
1912
- onChange: (e) => setFormData({
1913
- ...formData,
1914
- meta_description: e.target.value
1915
- }),
1916
- placeholder: "Campaign meta description for SEO"
1935
+ type: "button",
1936
+ variant: "secondary",
1937
+ onClick: () => setOpenProductModal(true),
1938
+ disabled,
1939
+ children: "Add Product"
1917
1940
  }
1918
1941
  )
1919
1942
  ] }),
1920
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1921
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "meta-keywords", children: "Meta Keywords" }),
1922
- /* @__PURE__ */ jsxRuntime.jsx(
1923
- ui.Input,
1924
- {
1925
- id: "meta-keywords",
1926
- value: formData.meta_keywords,
1927
- onChange: (e) => setFormData({
1928
- ...formData,
1929
- meta_keywords: e.target.value
1930
- }),
1931
- placeholder: "keyword1, keyword2, keyword3"
1932
- }
1933
- ),
1934
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Comma-separated keywords for SEO" })
1943
+ ((_a = errors.products) == null ? void 0 : _a.root) && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm", children: errors.products.root.message }),
1944
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
1945
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
1946
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Product" }),
1947
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Discount Type" }),
1948
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Discount Value" }),
1949
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Limit" }),
1950
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Max Qty per Order" }),
1951
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Actions" })
1952
+ ] }) }),
1953
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: fields.map((field, index) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
1954
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: field.product.title }),
1955
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
1956
+ reactHookForm.Controller,
1957
+ {
1958
+ name: `products.${index}.discountType`,
1959
+ control,
1960
+ render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsxs(
1961
+ ui.Select,
1962
+ {
1963
+ value: field2.value,
1964
+ onValueChange: field2.onChange,
1965
+ disabled,
1966
+ children: [
1967
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Select discount type" }) }),
1968
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "percentage", children: "Percentage" }) })
1969
+ ]
1970
+ }
1971
+ )
1972
+ }
1973
+ ) }),
1974
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
1975
+ reactHookForm.Controller,
1976
+ {
1977
+ name: `products.${index}.discountValue`,
1978
+ control,
1979
+ render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsx(
1980
+ ui.Input,
1981
+ {
1982
+ type: "number",
1983
+ value: field2.value,
1984
+ onChange: (e) => field2.onChange(Number(e.target.value)),
1985
+ disabled
1986
+ }
1987
+ )
1988
+ }
1989
+ ) }),
1990
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
1991
+ reactHookForm.Controller,
1992
+ {
1993
+ name: `products.${index}.limit`,
1994
+ control,
1995
+ render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsx(
1996
+ ui.Input,
1997
+ {
1998
+ type: "number",
1999
+ value: field2.value,
2000
+ onChange: (e) => field2.onChange(Number(e.target.value)),
2001
+ disabled
2002
+ }
2003
+ )
2004
+ }
2005
+ ) }),
2006
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
2007
+ reactHookForm.Controller,
2008
+ {
2009
+ name: `products.${index}.maxQty`,
2010
+ control,
2011
+ render: ({ field: field2 }) => /* @__PURE__ */ jsxRuntime.jsx(
2012
+ ui.Input,
2013
+ {
2014
+ type: "number",
2015
+ value: field2.value,
2016
+ onChange: (e) => field2.onChange(Number(e.target.value)),
2017
+ disabled
2018
+ }
2019
+ )
2020
+ }
2021
+ ) }),
2022
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(
2023
+ ui.Button,
2024
+ {
2025
+ type: "button",
2026
+ variant: "danger",
2027
+ onClick: () => remove(index),
2028
+ disabled,
2029
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
2030
+ }
2031
+ ) })
2032
+ ] }, field.id)) })
1935
2033
  ] }),
1936
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1937
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "display-order", children: "Display Order" }),
1938
- /* @__PURE__ */ jsxRuntime.jsx(
1939
- ui.Input,
1940
- {
1941
- id: "display-order",
1942
- type: "number",
1943
- value: formData.display_order,
1944
- onChange: (e) => setFormData({
1945
- ...formData,
1946
- display_order: parseInt(e.target.value) || 0
1947
- }),
1948
- placeholder: "0"
1949
- }
1950
- ),
1951
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-ui-fg-subtle", children: "Lower numbers appear first in listings" })
1952
- ] })
1953
- ] })
1954
- ] }),
1955
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end border-t pt-4", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSave, isLoading: isSaving, children: "Save Campaign Details" }) })
1956
- ] }),
1957
- showImageUploader && /* @__PURE__ */ jsxRuntime.jsx(
1958
- CampaignImageUploader,
1959
- {
1960
- campaignId,
1961
- imageType: showImageUploader,
1962
- currentImageUrl: showImageUploader === "image" ? campaignDetail == null ? void 0 : campaignDetail.image_url : campaignDetail == null ? void 0 : campaignDetail.thumbnail_url,
1963
- onClose: () => setShowImageUploader(null),
1964
- onSuccess: fetchCampaignDetail
2034
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled, children: "Save" })
2035
+ ]
1965
2036
  }
1966
- )
2037
+ ),
2038
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal, { open: openProductModal, onOpenChange: setOpenProductModal, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Content, { children: [
2039
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Header, { title: "Add Product" }),
2040
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Body, { children: /* @__PURE__ */ jsxRuntime.jsx(
2041
+ ProductSelector,
2042
+ {
2043
+ selectedProductIds: fields.map((f) => f.product.id),
2044
+ onSelectProduct: (product) => {
2045
+ append({
2046
+ product,
2047
+ discountType: "percentage",
2048
+ discountValue: 10,
2049
+ limit: 10,
2050
+ maxQty: 1
2051
+ });
2052
+ setOpenProductModal(false);
2053
+ }
2054
+ }
2055
+ ) })
2056
+ ] }) })
1967
2057
  ] });
1968
2058
  };
2059
+ const useFlashSaleById = (id) => {
2060
+ const [data, setData] = react.useState(null);
2061
+ const [isLoading, setIsLoading] = react.useState(true);
2062
+ const [error, setError] = react.useState(null);
2063
+ const fetchFlashSale = async () => {
2064
+ if (!id) {
2065
+ setIsLoading(false);
2066
+ return;
2067
+ }
2068
+ setIsLoading(true);
2069
+ setError(null);
2070
+ try {
2071
+ const response = await axios__default.default.get(`/admin/flash-sales/${id}`);
2072
+ setData(response.data);
2073
+ } catch (err) {
2074
+ setError(
2075
+ err instanceof Error ? err : new Error("Failed to fetch flash sale")
2076
+ );
2077
+ } finally {
2078
+ setIsLoading(false);
2079
+ }
2080
+ };
2081
+ react.useEffect(() => {
2082
+ fetchFlashSale();
2083
+ }, [id]);
2084
+ return {
2085
+ data,
2086
+ isLoading,
2087
+ error,
2088
+ refetch: fetchFlashSale
2089
+ };
2090
+ };
1969
2091
  const FlashSaleDetail = () => {
1970
2092
  const { id } = reactRouterDom.useParams();
1971
2093
  const navigate = reactRouterDom.useNavigate();
@@ -2048,7 +2170,7 @@ const FlashSaleDetail = () => {
2048
2170
  ] })
2049
2171
  ] });
2050
2172
  };
2051
- const config$1 = adminSdk.defineRouteConfig({
2173
+ const config = adminSdk.defineRouteConfig({
2052
2174
  label: "Flash Sale Detail",
2053
2175
  icon: icons.Sparkles
2054
2176
  });
@@ -2087,128 +2209,6 @@ const FlashSaleCreate = () => {
2087
2209
  }
2088
2210
  );
2089
2211
  };
2090
- const useCouponById = (id) => {
2091
- const [data, setData] = react.useState(null);
2092
- const [isLoading, setIsLoading] = react.useState(true);
2093
- const [error, setError] = react.useState(null);
2094
- const fetchCoupon = async () => {
2095
- if (!id) {
2096
- return;
2097
- }
2098
- setIsLoading(true);
2099
- setError(null);
2100
- try {
2101
- const response = await axios__default.default.get(`/admin/coupons/${id}`);
2102
- setData(response.data);
2103
- } catch (err) {
2104
- setError(err instanceof Error ? err : new Error("Failed to fetch coupon"));
2105
- } finally {
2106
- setIsLoading(false);
2107
- }
2108
- };
2109
- react.useEffect(() => {
2110
- fetchCoupon();
2111
- }, [id]);
2112
- return {
2113
- data,
2114
- isLoading,
2115
- error,
2116
- refetch: fetchCoupon
2117
- };
2118
- };
2119
- const CouponDetail = () => {
2120
- var _a, _b, _c, _d, _e, _f, _g;
2121
- const { id } = reactRouterDom.useParams();
2122
- const navigate = reactRouterDom.useNavigate();
2123
- const [isEditing, setIsEditing] = react.useState(false);
2124
- const { data, isLoading, refetch } = useCouponById(id ?? "");
2125
- if (isLoading) {
2126
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center h-screen", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent" }) });
2127
- }
2128
- if (!(data == null ? void 0 : data.campaign)) {
2129
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Coupon not found" }) });
2130
- }
2131
- const campaign = data.campaign;
2132
- const promotion = (_a = campaign.promotions) == null ? void 0 : _a[0];
2133
- const initialValues = {
2134
- name: campaign.name ?? "",
2135
- description: campaign.description ?? "",
2136
- type: "coupon",
2137
- code: (promotion == null ? void 0 : promotion.code) ?? "",
2138
- discount_type: ((_b = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _b.type) ?? "percentage",
2139
- discount_value: Number(((_c = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _c.value) ?? 0),
2140
- currency_code: ((_d = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _d.currency_code) ?? void 0,
2141
- starts_at: campaign.starts_at ? dayjs__default.default(campaign.starts_at).format("YYYY-MM-DDTHH:mm") : void 0,
2142
- ends_at: campaign.ends_at ? dayjs__default.default(campaign.ends_at).format("YYYY-MM-DDTHH:mm") : void 0,
2143
- allocation: ((_e = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _e.allocation) === "total" ? "across" : ((_f = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _f.allocation) ?? "across",
2144
- target_type: ((_g = promotion == null ? void 0 : promotion.application_method) == null ? void 0 : _g.target_type) ?? "order"
2145
- };
2146
- const handleSubmit = async (formData) => {
2147
- var _a2, _b2;
2148
- if (!id) {
2149
- return;
2150
- }
2151
- try {
2152
- await axios__default.default.put(`/admin/coupons/${id}`, {
2153
- ...formData,
2154
- starts_at: new Date(formData.starts_at).toUTCString(),
2155
- ends_at: new Date(formData.ends_at).toUTCString(),
2156
- currency_code: formData.discount_type === "fixed" ? formData.currency_code : void 0
2157
- });
2158
- ui.toast.success("Coupon updated successfully");
2159
- setIsEditing(false);
2160
- refetch();
2161
- } catch (error) {
2162
- let message = "Failed to update coupon";
2163
- if (axios__default.default.isAxiosError(error)) {
2164
- message = ((_b2 = (_a2 = error.response) == null ? void 0 : _a2.data) == null ? void 0 : _b2.message) ?? message;
2165
- } else if (error instanceof Error) {
2166
- message = error.message;
2167
- }
2168
- ui.toast.error("Failed to update coupon", {
2169
- description: message
2170
- });
2171
- }
2172
- };
2173
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
2174
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
2175
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2176
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold mb-1", children: campaign.name }),
2177
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle", children: "Manage coupon configuration and content" })
2178
- ] }),
2179
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
2180
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/coupons"), children: "Back to Coupons" }),
2181
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
2182
- ] })
2183
- ] }),
2184
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { defaultValue: "settings", children: [
2185
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
2186
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "settings", children: "Coupon Settings" }),
2187
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "details", children: "Coupon Details" })
2188
- ] }),
2189
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
2190
- CouponForm,
2191
- {
2192
- initialData: initialValues,
2193
- onSubmit: handleSubmit,
2194
- onCancel: () => navigate("/coupons"),
2195
- disabled: !isEditing
2196
- }
2197
- ) }),
2198
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
2199
- CampaignDetailForm,
2200
- {
2201
- campaignId: campaign.id,
2202
- campaignName: campaign.name ?? ""
2203
- }
2204
- ) })
2205
- ] })
2206
- ] });
2207
- };
2208
- const config = adminSdk.defineRouteConfig({
2209
- label: "Coupon Detail",
2210
- icon: icons.Tag
2211
- });
2212
2212
  const widgetModule = { widgets: [
2213
2213
  {
2214
2214
  Component: CampaignDetailWidget,
@@ -2225,6 +2225,10 @@ const routeModule = {
2225
2225
  Component: FlashSale,
2226
2226
  path: "/flash-sales"
2227
2227
  },
2228
+ {
2229
+ Component: CouponDetail,
2230
+ path: "/coupons/:id"
2231
+ },
2228
2232
  {
2229
2233
  Component: CouponCreate,
2230
2234
  path: "/coupons/create"
@@ -2236,10 +2240,6 @@ const routeModule = {
2236
2240
  {
2237
2241
  Component: FlashSaleCreate,
2238
2242
  path: "/flash-sales/create"
2239
- },
2240
- {
2241
- Component: CouponDetail,
2242
- path: "/coupons/:id"
2243
2243
  }
2244
2244
  ]
2245
2245
  };
@@ -2258,14 +2258,14 @@ const menuItemModule = {
2258
2258
  nested: void 0
2259
2259
  },
2260
2260
  {
2261
- label: config.label,
2262
- icon: config.icon,
2261
+ label: config$1.label,
2262
+ icon: config$1.icon,
2263
2263
  path: "/coupons/:id",
2264
2264
  nested: void 0
2265
2265
  },
2266
2266
  {
2267
- label: config$1.label,
2268
- icon: config$1.icon,
2267
+ label: config.label,
2268
+ icon: config.icon,
2269
2269
  path: "/flash-sales/:id",
2270
2270
  nested: void 0
2271
2271
  }