@lodashventure/medusa-campaign 1.5.0 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -698,1364 +698,1360 @@ const CouponForm = ({
698
698
  )
699
699
  ] });
700
700
  };
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);
701
+ const CouponCreate = () => {
702
+ const navigate = reactRouterDom.useNavigate();
703
+ const handleSubmit = async (formData) => {
704
+ var _a, _b;
711
705
  try {
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);
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
+ });
718
724
  }
719
725
  };
720
- react.useEffect(() => {
721
- fetchCoupon();
722
- }, [id]);
723
- return {
724
- data,
725
- isLoading,
726
- error,
727
- refetch: fetchCoupon
728
- };
726
+ return /* @__PURE__ */ jsxRuntime.jsx(
727
+ CouponForm,
728
+ {
729
+ onSubmit: handleSubmit,
730
+ onCancel: () => navigate("/coupons")
731
+ }
732
+ );
729
733
  };
730
- const MarkdownEditor = ({
731
- label,
732
- value,
733
- onChange,
734
- placeholder,
735
- helpText,
736
- rows = 10,
737
- showPreview = true
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
738
755
  }) => {
739
- const [activeTab, setActiveTab] = react.useState("write");
740
- const insertMarkdown = (before, after = "") => {
741
- const textarea = document.getElementById(
742
- `markdown-${label}`
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
+ 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)
743
783
  );
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);
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
+ ] });
804
+ };
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
837
+ }) => {
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
+ }
757
873
  };
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;
874
+ const onFormSubmit = (data) => {
875
+ onSubmit(data);
792
876
  };
793
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
794
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
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
- ] })
800
- ] }),
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",
922
- {
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>'
929
- }
930
- }
931
- ) })
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" })
992
- ] });
993
- };
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);
1004
- const [error, setError] = react.useState(null);
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;
1016
- }
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;
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);
1035
- try {
1036
- const response = await fetch(
1037
- `/admin/campaigns/${campaignId}/${imageType}`,
1038
- {
1039
- method: "POST",
1040
- body: formData,
1041
- credentials: "include"
1042
- }
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);
1062
- } finally {
1063
- setIsUploading(false);
1064
- }
1065
- };
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
- }
1077
- );
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
- }
1095
- };
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" })
1129
- ] }),
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: [
1136
- /* @__PURE__ */ jsxRuntime.jsx(
1137
- "img",
1138
- {
1139
- src: imageToDisplay,
1140
- alt: `Campaign ${imageType}`,
1141
- className: ui.clx(
1142
- "w-full object-contain",
1143
- isThumbnail ? "h-32" : "h-64"
1144
- )
1145
- }
1146
- ),
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(
1151
- ui.Button,
1152
- {
1153
- variant: "secondary",
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
- ]
1163
- }
1164
- ),
1165
- /* @__PURE__ */ jsxRuntime.jsxs(
1166
- ui.Button,
1167
- {
1168
- variant: "danger",
1169
- disabled: isUploading,
1170
- onClick: handleDelete,
1171
- children: [
1172
- /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "mr-2" }),
1173
- "Delete"
1174
- ]
1175
- }
1176
- )
1177
- ] })
1178
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
1179
- "div",
1180
- {
1181
- className: ui.clx(
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"
1186
- ),
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
- });
1282
- }
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");
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(", ");
1310
884
  }
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
- }
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
+ });
1317
890
  };
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" })
1329
- ] }),
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" }),
891
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
892
+ /* @__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" })
895
+ ] }),
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" }),
1335
968
  /* @__PURE__ */ jsxRuntime.jsx(
1336
- "div",
969
+ ui.Button,
1337
970
  {
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,
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,
1360
998
  {
1361
- variant: "secondary",
1362
- size: "small",
1363
- className: "absolute bottom-2 right-2",
1364
- onClick: () => setShowImageUploader("image"),
999
+ value: field2.value,
1000
+ onValueChange: field2.onChange,
1001
+ disabled,
1365
1002
  children: [
1366
- /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "mr-1" }),
1367
- "Change"
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" }) })
1368
1005
  ]
1369
1006
  }
1370
1007
  )
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",
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,
1397
1017
  {
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
- }
1018
+ type: "number",
1019
+ value: field2.value,
1020
+ onChange: (e) => field2.onChange(Number(e.target.value)),
1021
+ disabled
1408
1022
  }
1409
- ),
1410
- /* @__PURE__ */ jsxRuntime.jsxs(
1411
- ui.Button,
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,
1412
1033
  {
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
- ]
1034
+ type: "number",
1035
+ value: field2.value,
1036
+ onChange: (e) => field2.onChange(Number(e.target.value)),
1037
+ disabled
1421
1038
  }
1422
1039
  )
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
- ] })
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,
1078
+ {
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);
1089
+ }
1090
+ }
1091
+ ) })
1092
+ ] }) })
1093
+ ] });
1094
+ };
1095
+ const useFlashSaleById = (id) => {
1096
+ const [data, setData] = react.useState(null);
1097
+ const [isLoading, setIsLoading] = react.useState(true);
1098
+ const [error, setError] = react.useState(null);
1099
+ const fetchFlashSale = async () => {
1100
+ if (!id) {
1101
+ setIsLoading(false);
1102
+ return;
1103
+ }
1104
+ setIsLoading(true);
1105
+ setError(null);
1106
+ 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")
1112
+ );
1113
+ } finally {
1114
+ setIsLoading(false);
1115
+ }
1116
+ };
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
1152
+ );
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;
1189
+ };
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" })
1438
1202
  ] }),
1439
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx(
1440
- MarkdownEditor,
1441
- {
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
1449
- }
1450
- ) }),
1451
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx(
1452
- MarkdownEditor,
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
+ ),
1271
+ /* @__PURE__ */ jsxRuntime.jsx(
1272
+ ui.Button,
1273
+ {
1274
+ type: "button",
1275
+ variant: "secondary",
1276
+ size: "small",
1277
+ onClick: () => insertMarkdown("![alt](", ")"),
1278
+ title: "Image",
1279
+ children: "Image"
1280
+ }
1281
+ ),
1282
+ /* @__PURE__ */ jsxRuntime.jsx(
1283
+ ui.Button,
1284
+ {
1285
+ type: "button",
1286
+ variant: "secondary",
1287
+ size: "small",
1288
+ onClick: () => insertMarkdown("```\n", "\n```"),
1289
+ title: "Code Block",
1290
+ children: "Code"
1291
+ }
1292
+ ),
1293
+ /* @__PURE__ */ jsxRuntime.jsx(
1294
+ ui.Button,
1295
+ {
1296
+ type: "button",
1297
+ variant: "secondary",
1298
+ size: "small",
1299
+ onClick: () => insertMarkdown("- "),
1300
+ title: "List",
1301
+ children: "List"
1302
+ }
1303
+ )
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(
1318
+ "div",
1453
1319
  {
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
- ] })
1320
+ 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"
1323
+ ),
1324
+ dangerouslySetInnerHTML: {
1325
+ __html: value ? renderMarkdownPreview(value) : '<p class="text-ui-fg-subtle">Nothing to preview</p>'
1326
+ }
1327
+ }
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
+ )
1557
1375
  ] }),
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" }) })
1376
+ /* @__PURE__ */ jsxRuntime.jsx(
1377
+ ui.Textarea,
1378
+ {
1379
+ id: `markdown-${label}`,
1380
+ value,
1381
+ onChange: (e) => onChange(e.target.value),
1382
+ placeholder,
1383
+ rows,
1384
+ className: "font-mono text-sm"
1385
+ }
1386
+ )
1559
1387
  ] }),
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
- )
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" })
1570
1389
  ] });
1571
1390
  };
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"
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";
1598
1408
  };
1599
- const handleSubmit = async (formData) => {
1600
- var _a2, _b2;
1601
- if (!id) {
1602
- return;
1409
+ const validateFile = (file) => {
1410
+ if (!allowedTypes.includes(file.type)) {
1411
+ setError(`Please upload a valid image file (${formatFileTypes()})`);
1412
+ return false;
1603
1413
  }
1604
- try {
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;
1620
- }
1621
- ui.toast.error("Failed to update coupon", {
1622
- description: message
1623
- });
1414
+ if (file.size > maxSize * 1024 * 1024) {
1415
+ setError(`File size must be less than ${maxSize}MB`);
1416
+ return false;
1624
1417
  }
1418
+ return true;
1625
1419
  };
1626
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
1627
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
1628
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
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" })
1631
- ] }),
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
- ] })
1636
- ] }),
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,
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}`,
1644
1435
  {
1645
- initialData: initialValues,
1646
- onSubmit: handleSubmit,
1647
- onCancel: () => navigate("/coupons"),
1648
- disabled: !isEditing
1436
+ method: "POST",
1437
+ body: formData,
1438
+ credentials: "include"
1649
1439
  }
1650
- ) }),
1651
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
1652
- CampaignDetailForm,
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);
1461
+ }
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
+ try {
1468
+ const response = await fetch(
1469
+ `/admin/campaigns/${campaignId}/${imageType}`,
1653
1470
  {
1654
- campaignId: campaign.id,
1655
- campaignName: campaign.name ?? ""
1471
+ method: "DELETE",
1472
+ credentials: "include"
1656
1473
  }
1657
- ) })
1658
- ] })
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;
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}`);
1684
1485
  }
1685
- ui.toast.error("Failed to create coupon", {
1686
- description: message
1687
- });
1486
+ } catch (err) {
1487
+ setError(`Error deleting ${imageType}`);
1488
+ console.error(`Error deleting ${imageType}:`, err);
1489
+ } finally {
1490
+ setIsUploading(false);
1688
1491
  }
1689
1492
  };
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(() => {
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) => {
1744
1508
  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);
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);
1755
1515
  }
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
- ] });
1768
- };
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
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: [
1519
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
1520
+ /* @__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" })
1526
+ ] }),
1527
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(icons.X, {}) })
1528
+ ] }),
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",
1577
+ {
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
+ ]
1606
+ }
1607
+ ),
1608
+ /* @__PURE__ */ jsxRuntime.jsx(
1609
+ "input",
1610
+ {
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"
1621
+ }
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" })
1627
+ ] })
1628
+ ] }) });
1629
+ };
1630
+ const CampaignDetailForm = ({
1631
+ campaignId,
1632
+ campaignName
1801
1633
  }) => {
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
- }
1820
- });
1821
- const { fields, append, remove, update } = reactHookForm.useFieldArray({
1822
- control,
1823
- name: "products"
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
1824
1649
  });
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}`);
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);
1836
1687
  }
1837
1688
  };
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(", ");
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");
1848
1707
  }
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
- });
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
+ }
1854
1714
  };
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,
1892
- {
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"
1898
- }
1899
- )
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
- }
1915
- ),
1916
- /* @__PURE__ */ jsxRuntime.jsx(
1917
- ui.Input,
1918
- {
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"
1924
- }
1925
- )
1926
- ] }),
1927
- errors.ends_at && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-500 text-sm mt-1", children: errors.ends_at.message })
1928
- ] })
1929
- ] }),
1930
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
1931
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Products" }),
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" }),
1932
1732
  /* @__PURE__ */ jsxRuntime.jsx(
1933
- ui.Button,
1733
+ "div",
1934
1734
  {
1935
- type: "button",
1936
- variant: "secondary",
1937
- onClick: () => setOpenProductModal(true),
1938
- disabled,
1939
- children: "Add Product"
1940
- }
1941
- )
1942
- ] }),
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,
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",
1962
1742
  {
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
- ]
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
+ }
1970
1753
  }
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,
1754
+ ),
1755
+ /* @__PURE__ */ jsxRuntime.jsxs(
1756
+ ui.Button,
1981
1757
  {
1982
- type: "number",
1983
- value: field2.value,
1984
- onChange: (e) => field2.onChange(Number(e.target.value)),
1985
- disabled
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
+ ]
1986
1766
  }
1987
1767
  )
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,
1768
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
1769
+ ui.Button,
1770
+ {
1771
+ variant: "secondary",
1772
+ onClick: () => setShowImageUploader("image"),
1773
+ children: [
1774
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "mr-2" }),
1775
+ "Upload Image"
1776
+ ]
1777
+ }
1778
+ )
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"
1790
+ ),
1791
+ children: (campaignDetail == null ? void 0 : campaignDetail.thumbnail_url) ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1792
+ /* @__PURE__ */ jsxRuntime.jsx(
1793
+ "img",
1997
1794
  {
1998
- type: "number",
1999
- value: field2.value,
2000
- onChange: (e) => field2.onChange(Number(e.target.value)),
2001
- disabled
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
+ }
2002
1805
  }
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,
1806
+ ),
1807
+ /* @__PURE__ */ jsxRuntime.jsxs(
1808
+ ui.Button,
2013
1809
  {
2014
- type: "number",
2015
- value: field2.value,
2016
- onChange: (e) => field2.onChange(Number(e.target.value)),
2017
- disabled
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
+ ]
2018
1818
  }
2019
1819
  )
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)) })
1820
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
1821
+ ui.Button,
1822
+ {
1823
+ variant: "secondary",
1824
+ onClick: () => setShowImageUploader("thumbnail"),
1825
+ children: [
1826
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PhotoSolid, { className: "mr-2" }),
1827
+ "Upload Thumbnail"
1828
+ ]
1829
+ }
1830
+ )
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
+ )
2033
1875
  ] }),
2034
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", disabled, children: "Save" })
2035
- ]
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
+ )
1904
+ ] }),
1905
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1906
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "meta-description", children: "Meta Description" }),
1907
+ /* @__PURE__ */ jsxRuntime.jsx(
1908
+ ui.Input,
1909
+ {
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"
1917
+ }
1918
+ )
1919
+ ] }),
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" })
1935
+ ] }),
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
2036
1965
  }
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,
1966
+ )
1967
+ ] });
1968
+ };
1969
+ const FlashSaleDetail = () => {
1970
+ const { id } = reactRouterDom.useParams();
1971
+ const navigate = reactRouterDom.useNavigate();
1972
+ const { data, isLoading, refetch } = useFlashSaleById(id || "");
1973
+ const [isEditing, setIsEditing] = react.useState(false);
1974
+ async function handleSubmit(campaignData2) {
1975
+ var _a;
1976
+ try {
1977
+ await axios__default.default.put(`/admin/flash-sales/${id}`, {
1978
+ ...campaignData2,
1979
+ starts_at: new Date(campaignData2.starts_at).toUTCString(),
1980
+ ends_at: new Date(campaignData2.ends_at).toUTCString()
1981
+ });
1982
+ ui.toast.success("Flash sale updated successfully");
1983
+ refetch();
1984
+ navigate("/flash-sales");
1985
+ } catch (error) {
1986
+ let message;
1987
+ if (axios__default.default.isAxiosError(error)) {
1988
+ console.log(error);
1989
+ message = (_a = error.response) == null ? void 0 : _a.data.message;
1990
+ } else if (error instanceof Error) {
1991
+ message = error.message;
1992
+ } else {
1993
+ message = "Failed to update flash sale";
1994
+ }
1995
+ ui.toast.error("Failed to update flash sale", {
1996
+ description: message
1997
+ });
1998
+ }
1999
+ }
2000
+ if (isLoading) {
2001
+ 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" }) });
2002
+ }
2003
+ if (!data) {
2004
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Flash sale not found" }) });
2005
+ }
2006
+ const campaignData = {
2007
+ name: data.name || "",
2008
+ description: data.description || "",
2009
+ type: "flash-sale",
2010
+ starts_at: data.starts_at,
2011
+ ends_at: data.ends_at
2012
+ };
2013
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
2014
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
2015
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2016
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold mb-1", children: data.name }),
2017
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle", children: "Manage campaign settings and content" })
2018
+ ] }),
2019
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
2020
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/flash-sales"), children: "Back to Campaigns" }),
2021
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
2022
+ ] })
2023
+ ] }),
2024
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { defaultValue: "settings", children: [
2025
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
2026
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "settings", children: "Campaign Settings" }),
2027
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "details", children: "Campaign Details" })
2028
+ ] }),
2029
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
2030
+ FlashSaleForm,
2042
2031
  {
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
- }
2032
+ initialData: campaignData,
2033
+ initialProducts: new Map(
2034
+ data.products.map((product) => [product.product.id, product])
2035
+ ),
2036
+ onSubmit: handleSubmit,
2037
+ onCancel: () => navigate("/flash-sales"),
2038
+ disabled: !isEditing
2039
+ }
2040
+ ) }),
2041
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
2042
+ CampaignDetailForm,
2043
+ {
2044
+ campaignId: id || "",
2045
+ campaignName: data.name || ""
2054
2046
  }
2055
2047
  ) })
2056
- ] }) })
2048
+ ] })
2057
2049
  ] });
2058
2050
  };
2051
+ const config$1 = adminSdk.defineRouteConfig({
2052
+ label: "Flash Sale Detail",
2053
+ icon: icons.Sparkles
2054
+ });
2059
2055
  const FlashSaleCreate = () => {
2060
2056
  const navigate = reactRouterDom.useNavigate();
2061
2057
  async function handleSubmit(campaignData) {
@@ -2091,123 +2087,127 @@ const FlashSaleCreate = () => {
2091
2087
  }
2092
2088
  );
2093
2089
  };
2094
- const useFlashSaleById = (id) => {
2090
+ const useCouponById = (id) => {
2095
2091
  const [data, setData] = react.useState(null);
2096
2092
  const [isLoading, setIsLoading] = react.useState(true);
2097
2093
  const [error, setError] = react.useState(null);
2098
- const fetchFlashSale = async () => {
2094
+ const fetchCoupon = async () => {
2099
2095
  if (!id) {
2100
- setIsLoading(false);
2101
2096
  return;
2102
2097
  }
2103
2098
  setIsLoading(true);
2104
2099
  setError(null);
2105
2100
  try {
2106
- const response = await axios__default.default.get(`/admin/flash-sales/${id}`);
2101
+ const response = await axios__default.default.get(`/admin/coupons/${id}`);
2107
2102
  setData(response.data);
2108
2103
  } catch (err) {
2109
- setError(
2110
- err instanceof Error ? err : new Error("Failed to fetch flash sale")
2111
- );
2104
+ setError(err instanceof Error ? err : new Error("Failed to fetch coupon"));
2112
2105
  } finally {
2113
2106
  setIsLoading(false);
2114
2107
  }
2115
2108
  };
2116
2109
  react.useEffect(() => {
2117
- fetchFlashSale();
2110
+ fetchCoupon();
2118
2111
  }, [id]);
2119
2112
  return {
2120
2113
  data,
2121
2114
  isLoading,
2122
2115
  error,
2123
- refetch: fetchFlashSale
2116
+ refetch: fetchCoupon
2124
2117
  };
2125
2118
  };
2126
- const FlashSaleDetail = () => {
2119
+ const CouponDetail = () => {
2120
+ var _a, _b, _c, _d, _e, _f, _g;
2127
2121
  const { id } = reactRouterDom.useParams();
2128
2122
  const navigate = reactRouterDom.useNavigate();
2129
- const { data, isLoading, refetch } = useFlashSaleById(id || "");
2130
2123
  const [isEditing, setIsEditing] = react.useState(false);
2131
- async function handleSubmit(campaignData2) {
2132
- var _a;
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
+ }
2133
2151
  try {
2134
- await axios__default.default.put(`/admin/flash-sales/${id}`, {
2135
- ...campaignData2,
2136
- starts_at: new Date(campaignData2.starts_at).toUTCString(),
2137
- ends_at: new Date(campaignData2.ends_at).toUTCString()
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
2138
2157
  });
2139
- ui.toast.success("Flash sale updated successfully");
2158
+ ui.toast.success("Coupon updated successfully");
2159
+ setIsEditing(false);
2140
2160
  refetch();
2141
- navigate("/flash-sales");
2142
2161
  } catch (error) {
2143
- let message;
2162
+ let message = "Failed to update coupon";
2144
2163
  if (axios__default.default.isAxiosError(error)) {
2145
- console.log(error);
2146
- message = (_a = error.response) == null ? void 0 : _a.data.message;
2164
+ message = ((_b2 = (_a2 = error.response) == null ? void 0 : _a2.data) == null ? void 0 : _b2.message) ?? message;
2147
2165
  } else if (error instanceof Error) {
2148
2166
  message = error.message;
2149
- } else {
2150
- message = "Failed to update flash sale";
2151
2167
  }
2152
- ui.toast.error("Failed to update flash sale", {
2168
+ ui.toast.error("Failed to update coupon", {
2153
2169
  description: message
2154
2170
  });
2155
2171
  }
2156
- }
2157
- if (isLoading) {
2158
- 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" }) });
2159
- }
2160
- if (!data) {
2161
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Flash sale not found" }) });
2162
- }
2163
- const campaignData = {
2164
- name: data.name || "",
2165
- description: data.description || "",
2166
- type: "flash-sale",
2167
- starts_at: data.starts_at,
2168
- ends_at: data.ends_at
2169
2172
  };
2170
2173
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
2171
2174
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
2172
2175
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2173
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold mb-1", children: data.name }),
2174
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-ui-fg-subtle", children: "Manage campaign settings and content" })
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" })
2175
2178
  ] }),
2176
2179
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
2177
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/flash-sales"), children: "Back to Campaigns" }),
2180
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/coupons"), children: "Back to Coupons" }),
2178
2181
  /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => setIsEditing((prev) => !prev), children: isEditing ? "View Mode" : "Edit Mode" })
2179
2182
  ] })
2180
2183
  ] }),
2181
2184
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { defaultValue: "settings", children: [
2182
2185
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
2183
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "settings", children: "Campaign Settings" }),
2184
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "details", children: "Campaign Details" })
2186
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "settings", children: "Coupon Settings" }),
2187
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "details", children: "Coupon Details" })
2185
2188
  ] }),
2186
2189
  /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "settings", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
2187
- FlashSaleForm,
2190
+ CouponForm,
2188
2191
  {
2189
- initialData: campaignData,
2190
- initialProducts: new Map(
2191
- data.products.map((product) => [product.product.id, product])
2192
- ),
2192
+ initialData: initialValues,
2193
2193
  onSubmit: handleSubmit,
2194
- onCancel: () => navigate("/flash-sales"),
2194
+ onCancel: () => navigate("/coupons"),
2195
2195
  disabled: !isEditing
2196
2196
  }
2197
2197
  ) }),
2198
2198
  /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Content, { value: "details", className: "mt-6", children: /* @__PURE__ */ jsxRuntime.jsx(
2199
2199
  CampaignDetailForm,
2200
2200
  {
2201
- campaignId: id || "",
2202
- campaignName: data.name || ""
2201
+ campaignId: campaign.id,
2202
+ campaignName: campaign.name ?? ""
2203
2203
  }
2204
2204
  ) })
2205
2205
  ] })
2206
2206
  ] });
2207
2207
  };
2208
2208
  const config = adminSdk.defineRouteConfig({
2209
- label: "Flash Sale Detail",
2210
- icon: icons.Sparkles
2209
+ label: "Coupon Detail",
2210
+ icon: icons.Tag
2211
2211
  });
2212
2212
  const widgetModule = { widgets: [
2213
2213
  {
@@ -2225,21 +2225,21 @@ const routeModule = {
2225
2225
  Component: FlashSale,
2226
2226
  path: "/flash-sales"
2227
2227
  },
2228
- {
2229
- Component: CouponDetail,
2230
- path: "/coupons/:id"
2231
- },
2232
2228
  {
2233
2229
  Component: CouponCreate,
2234
2230
  path: "/coupons/create"
2235
2231
  },
2232
+ {
2233
+ Component: FlashSaleDetail,
2234
+ path: "/flash-sales/:id"
2235
+ },
2236
2236
  {
2237
2237
  Component: FlashSaleCreate,
2238
2238
  path: "/flash-sales/create"
2239
2239
  },
2240
2240
  {
2241
- Component: FlashSaleDetail,
2242
- path: "/flash-sales/:id"
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$1.label,
2262
- icon: config$1.icon,
2261
+ label: config.label,
2262
+ icon: config.icon,
2263
2263
  path: "/coupons/:id",
2264
2264
  nested: void 0
2265
2265
  },
2266
2266
  {
2267
- label: config.label,
2268
- icon: config.icon,
2267
+ label: config$1.label,
2268
+ icon: config$1.icon,
2269
2269
  path: "/flash-sales/:id",
2270
2270
  nested: void 0
2271
2271
  }